Tĩnh Vs. Liên kết động trong Java


103

Tôi hiện đang làm một bài tập cho một trong các lớp của mình và trong đó, tôi phải đưa ra các ví dụ, sử dụng cú pháp Java, về liên kết tĩnhđộng .

Tôi hiểu khái niệm cơ bản, rằng liên kết tĩnh xảy ra tại thời gian biên dịch và liên kết động xảy ra trong thời gian chạy, nhưng tôi không thể tìm ra cách chúng thực sự hoạt động cụ thể.

Tôi đã tìm thấy một ví dụ về ràng buộc tĩnh trực tuyến đưa ra ví dụ này:

public static void callEat(Animal animal) {
    System.out.println("Animal is eating");
}

public static void callEat(Dog dog) {
    System.out.println("Dog is eating");
}

public static void main(String args[])
{
    Animal a = new Dog();
    callEat(a);
}

Và điều này sẽ in "động vật đang ăn" bởi vì lệnh gọi callEatsử dụng liên kết tĩnh , nhưng tôi không chắc tại sao điều này được coi là liên kết tĩnh.

Cho đến nay không có nguồn nào mà tôi đã xem có thể giải thích điều này theo cách mà tôi có thể làm theo.



1
Lưu ý rằng có một số khái niệm khác nhau được gọi là "ràng buộc". Trong trường hợp cụ thể này, đối với kiểu ràng buộc này (liên quan đến sự lựa chọn giữa các "chữ ký" phương thức tương tự nhưng không giống nhau) trình biên dịch (đưa ra quyết định "tĩnh", vì chúng không thay đổi tại thời điểm chạy) quyết định rằng tham số là một "Động vật" vì đó là kiểu (tĩnh) của biến "a".
Hot Licks

(Có những ngôn ngữ mà lựa chọn chữ ký phương pháp cụ thể sẽ bị bỏ lại cho đến khi thời gian chạy, và callEat (Chó) sẽ được chọn.)
Hot Licks

Câu trả lời:


114

Từ bài đăng trên blog của Javarevisited :

Dưới đây là một số khác biệt quan trọng giữa ràng buộc tĩnh và động:

  1. Liên kết tĩnh trong Java xảy ra trong thời gian biên dịch trong khi liên kết động xảy ra trong thời gian chạy.
  2. private, finalstaticcác phương thức và biến sử dụng liên kết tĩnh và được liên kết bởi trình biên dịch trong khi các phương thức ảo được liên kết trong thời gian chạy dựa trên đối tượng thời gian chạy.
  3. Liên kết tĩnh sử dụng thông tin Type( classtrong Java) để liên kết trong khi liên kết động sử dụng đối tượng để giải quyết ràng buộc.
  4. Các phương thức bị ghi đè được liên kết bằng cách sử dụng liên kết tĩnh trong khi các phương thức bị ghi đè được liên kết bằng cách sử dụng liên kết động trong thời gian chạy.

Đây là một ví dụ sẽ giúp bạn hiểu cả ràng buộc tĩnh và động trong Java.

Ví dụ về ràng buộc tĩnh trong Java

public class StaticBindingTest {  
    public static void main(String args[]) {
        Collection c = new HashSet();
        StaticBindingTest et = new StaticBindingTest();
        et.sort(c);
    }
    //overloaded method takes Collection argument
    public Collection sort(Collection c) {
        System.out.println("Inside Collection sort method");
        return c;
    }
    //another overloaded method which takes HashSet argument which is sub class
    public Collection sort(HashSet hs) {
        System.out.println("Inside HashSet sort method");
        return hs;
    }
}

Đầu ra : Phương pháp sắp xếp Inside Collection

Ví dụ về Liên kết động trong Java

public class DynamicBindingTest {   
    public static void main(String args[]) {
        Vehicle vehicle = new Car(); //here Type is vehicle but object will be Car
        vehicle.start(); //Car's start called because start() is overridden method
    }
}

class Vehicle {
    public void start() {
        System.out.println("Inside start method of Vehicle");
    }
}

class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Inside start method of Car");
    }
}

Đầu ra: Phương pháp khởi động bên trong xe



11
Tôi vẫn không hiểu sự khác biệt,
technazi

9
@technazi static binding chỉ xem xét kiểu (những gì đã từng có trước dấu bằng, ví dụ: Collection c = new HashSet (); vì vậy nó sẽ được xem chỉ là một đối tượng collection khi nó là một hashset). Liên kết Dyanmic tính đến đối tượng thực tế (là gì sau dấu bằng để nó thực sự nhận ra một HashSet của nó).
Đánh dấu

22

Kết nối một lệnh gọi phương thức với thân phương thức được gọi là Binding. Như Maulik đã nói "Liên kết tĩnh sử dụng thông tin Kiểu (Lớp trong Java) để liên kết trong khi Liên kết động sử dụng Đối tượng để giải quyết ràng buộc." Vì vậy, mã này:

public class Animal {
    void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {
        Animal a = new Dog();
        a.eat(); // prints >> dog is eating...
    }

    @Override
    void eat() {
        System.out.println("dog is eating...");
    }
}

Sẽ cho ra kết quả: dog is eat ... vì nó đang sử dụng tham chiếu đối tượng để tìm cách sử dụng. Nếu chúng tôi thay đổi mã trên thành mã này:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

Nó sẽ tạo ra: động vật đang ăn ... bởi vì nó là một phương thức tĩnh, vì vậy nó đang sử dụng Kiểu (trong trường hợp này là Động vật) để giải quyết phương thức tĩnh nào sẽ gọi. Bên cạnh các phương thức tĩnh private và các phương thức cuối cùng sử dụng cùng một cách tiếp cận.


1
Tại sao Java không thể suy luận rằng đó athực sự là Doglúc biên dịch?
Minh Nghĩa

4

Trình biên dịch chỉ biết rằng kiểu của "a" là Animal; điều này xảy ra tại thời điểm biên dịch, vì nó được gọi là ràng buộc tĩnh (Nạp ​​chồng phương thức). Nhưng nếu nó là ràng buộc động thì nó sẽ gọi Dogphương thức lớp. Đây là một ví dụ về ràng buộc động.

public class DynamicBindingTest {

    public static void main(String args[]) {
        Animal a= new Dog(); //here Type is Animal but object will be Dog
        a.eat();       //Dog's eat called because eat() is overridden method
    }
}

class Animal {

    public void eat() {
        System.out.println("Inside eat method of Animal");
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("Inside eat method of Dog");
    }
}

Đầu ra: Phương pháp ăn bên trong của chó


Điều này sẽ không gây ra lỗi biên dịch chẳng hạn như "Không thể tham chiếu một lớp / phương thức không tĩnh từ ngữ cảnh tĩnh"? Tôi luôn bối rối với điều đó, trong tâm trí rằng chính là tĩnh. Cảm ơn trước.
Amnor

3

Để hiểu rõ ràng buộc tĩnh và động thực sự hoạt động như thế nào? hoặc làm thế nào chúng được xác định bởi trình biên dịch và JVM?

Hãy lấy ví dụ dưới đây, đâu Mammallà lớp cha có một phương thức speak()Humanlớp mở rộng Mammal, ghi đè speak()phương thức và sau đó lại nạp chồng bằng speak(String language).

public class OverridingInternalExample {

    private static class Mammal {
        public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
    }

    private static class Human extends Mammal {

        @Override
        public void speak() { System.out.println("Hello"); }

        // Valid overload of speak
        public void speak(String language) {
            if (language.equals("Hindi")) System.out.println("Namaste");
            else System.out.println("Hello");
        }

        @Override
        public String toString() { return "Human Class"; }

    }

    //  Code below contains the output and bytecode of the method calls
    public static void main(String[] args) {
        Mammal anyMammal = new Mammal();
        anyMammal.speak();  // Output - ohlllalalalalalaoaoaoa
        // 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Mammal humanMammal = new Human();
        humanMammal.speak(); // Output - Hello
        // 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Human human = new Human();
        human.speak(); // Output - Hello
        // 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V

        human.speak("Hindi"); // Output - Namaste
        // 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
    }
}

Khi chúng ta biên dịch đoạn mã trên và cố gắng xem xét mã bytecode bằng cách sử dụng javap -verbose OverridingInternalExample, chúng ta có thể thấy trình biên dịch đó tạo ra một bảng hằng số, nơi nó gán mã số nguyên cho mọi lệnh gọi phương thức và mã byte cho chương trình mà tôi đã trích xuất và đưa vào chính chương trình ( xem các bình luận bên dưới mỗi lần gọi phương thức)

Chương trình Bytecode

Bằng cách nhìn vào đoạn code trên chúng ta có thể thấy rằng các bytecode của humanMammal.speak(), human.speak()human.speak("Hindi")là hoàn toàn khác nhau ( invokevirtual #4, invokevirtual #7, invokevirtual #9) vì trình biên dịch có thể phân biệt giữa chúng dựa trên danh sách đối số và tham chiếu lớp. Bởi vì tất cả những điều này được giải quyết tại thời điểm biên dịch tĩnh, đó là lý do tại sao Phương pháp Nạp chồng được gọi là Đa hình tĩnh hoặc Liên kết tĩnh .

Nhưng bytecode cho anyMammal.speak()humanMammal.speak()giống nhau ( invokevirtual #4) bởi vì theo trình biên dịch, cả hai phương thức đều được gọi trên Mammaltham chiếu.

Vì vậy, bây giờ câu hỏi đặt ra là nếu cả hai cuộc gọi phương thức có cùng một mã bytecode thì làm thế nào JVM biết phương thức nào để gọi?

Chà, câu trả lời được ẩn trong chính mã bytecode và nó là invokevirtualtập lệnh. JVM sử dụng invokevirtuallệnh để gọi Java tương đương với các phương thức ảo C ++. Trong C ++, nếu chúng ta muốn ghi đè một phương thức trong một lớp khác, chúng ta cần khai báo nó là ảo, Nhưng trong Java, tất cả các phương thức đều là ảo theo mặc định vì chúng ta có thể ghi đè mọi phương thức trong lớp con (trừ phương thức private, final và static).

Trong Java, mọi biến tham chiếu đều chứa hai con trỏ ẩn

  1. Một con trỏ tới một bảng giữ các phương thức của đối tượng và một con trỏ tới đối tượng Lớp. ví dụ: [speak (), speak (Chuỗi) Đối tượng lớp]
  2. Một con trỏ tới bộ nhớ được cấp phát trên heap cho dữ liệu của đối tượng đó, ví dụ như giá trị của các biến cá thể.

Vì vậy, tất cả các tham chiếu đối tượng gián tiếp giữ một tham chiếu đến một bảng chứa tất cả các tham chiếu phương thức của đối tượng đó. Java đã mượn khái niệm này từ C ++ và bảng này được gọi là bảng ảo (vtable).

Một vtable là một cấu trúc giống như mảng chứa các tên phương thức ảo và các tham chiếu của chúng trên các chỉ số mảng. JVM chỉ tạo một vtable cho mỗi lớp khi nó tải lớp đó vào bộ nhớ.

Vì vậy, bất cứ khi nào JVM gặp phải một invokevirtualtập lệnh, nó sẽ kiểm tra vtable của lớp đó để tìm tham chiếu phương thức và gọi phương thức cụ thể mà trong trường hợp của chúng ta là phương thức từ một đối tượng không phải là tham chiếu.

Bởi vì tất cả những điều này chỉ được giải quyết trong thời gian chạy và trong thời gian chạy, JVM biết phương thức nào cần gọi, đó là lý do tại sao Ghi đè phương thức được gọi là Đa hình động hoặc đơn giản là Đa hình hoặc Liên kết động .

Bạn có thể đọc thêm chi tiết trên bài viết của tôi Cách xử lý quá tải và ghi đè phương pháp JVM trong nội bộ .


2

Có ba điểm khác biệt chính giữa ràng buộc tĩnh và động trong khi thiết kế trình biên dịch và cách các biếnthủ tục được chuyển sang môi trường thời gian chạy . Những khác biệt này như sau:

Liên kết tĩnh : Trong liên kết tĩnh, ba vấn đề sau được thảo luận:

  • Định nghĩa về một thủ tục

  • Khai báo tên (biến, v.v.)

  • Phạm vi khai báo

Ràng buộc động : Ba vấn đề gặp phải trong liên kết động như sau:

  • Kích hoạt một thủ tục

  • Ràng buộc của một cái tên

  • Thời gian tồn tại của một ràng buộc


1

Với phương thức tĩnh trong lớp cha và lớp con: Static Binding

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child(); 
        pc.start(); 
    }
}

class parent {
    static public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

    static public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of parent

Ràng buộc động:

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child();
        pc.start(); 
    }
}

class parent {
   public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

   public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of child

0

Tất cả các câu trả lời ở đây đều đúng nhưng tôi muốn bổ sung điều gì đó còn thiếu. khi bạn đang ghi đè một phương thức tĩnh, có vẻ như chúng ta đang ghi đè nó nhưng thực ra nó không phải là ghi đè phương thức. Thay vào đó nó được gọi là phương thức ẩn. Không thể ghi đè các phương thức tĩnh trong Java.

Hãy xem ví dụ dưới đây:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

Trong ràng buộc động, phương thức được gọi tùy thuộc vào kiểu tham chiếu chứ không phải kiểu đối tượng mà biến tham chiếu đang giữ Ở đây ràng buộc tĩnh xảy ra bởi vì ẩn phương thức không phải là một đa hình động. Nếu bạn xóa từ khóa static trước eat () và đặt nó thành một phương thức không phải static thì nó sẽ hiển thị cho bạn tính đa hình động chứ không phải ẩn phương thức.

tôi tìm thấy liên kết dưới đây để hỗ trợ câu trả lời của tôi: https://youtu.be/tNgZpn7AeP0


0

Trong trường hợp loại liên kết tĩnh của đối tượng được xác định tại thời điểm biên dịch, trong khi loại liên kết động của đối tượng được xác định trong thời gian chạy.



class Dainamic{

    void run2(){
        System.out.println("dainamic_binding");
    }

}


public class StaticDainamicBinding extends Dainamic {

    void run(){
        System.out.println("static_binding");
    }

    @Override
    void run2() {
        super.run2();
    }

    public static void main(String[] args) {
        StaticDainamicBinding st_vs_dai = new StaticDainamicBinding();
        st_vs_dai.run();
        st_vs_dai.run2();
    }

}

-3

Bởi vì trình biên dịch biết ràng buộc tại thời điểm biên dịch. Ví dụ: nếu bạn gọi một phương thức trên một giao diện, thì trình biên dịch không thể biết và ràng buộc được giải quyết trong thời gian chạy bởi vì đối tượng thực tế có một phương thức được gọi trên đó có thể là một trong số. Do đó, đó là thời gian chạy hoặc ràng buộc động.

Lời gọi của bạn được liên kết với lớp Animal tại thời điểm biên dịch vì bạn đã chỉ định kiểu. Nếu bạn chuyển biến đó vào một phương thức khác ở một nơi khác, sẽ không ai biết (ngoài bạn vì bạn đã viết nó) nó sẽ là lớp thực sự nào. Manh mối duy nhất là loại Động vật được khai báo.


1
Không đúng. Trình biên dịch sẽ đưa ra quyết định chính xác tương tự nếu bạn đang thực hiện cuộc gọi trên một giao diện.
Hot Licks

@HotLicks Quyết định chính xác giống như những gì? Nếu bạn biên dịch một lớp để gọi một phương thức foo (String str) trên một giao diện, trình biên dịch không thể biết tại thời điểm biên dịch mà phương thức foo (String str) sẽ được gọi đến lớp nào. Chỉ trong thời gian chạy phương thức có thể bị ràng buộc với một triển khai lớp cụ thể.
Aaron,

Nhưng ràng buộc tĩnh với chữ ký phương thức cụ thể vẫn sẽ xảy ra. Trình biên dịch sẽ vẫn chọn callEat (Động vật) thay vì callEat (Chó).
Hot Licks

@HotLicks Chắc chắn rồi, nhưng đó không phải là câu hỏi tôi đã trả lời. Có lẽ tôi đã hiểu sai: DI so sánh nó với việc gọi trên một giao diện để làm nổi bật rằng tại thời điểm biên dịch, trình biên dịch không thể biết liệu bạn có thực sự khởi tạo một lớp con / việc triển khai khác hay không.
Aaron,

Trên thực tế, tại thời điểm biên dịch, trình biên dịch có thể (trong trường hợp này) rất dễ dàng có thể biết rằng "a" là Chó. Trong thực tế, nó có thể phải mất một thời gian dài để "quên" điều đó.
Hot Licks
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.