113

我目前正在为我的一个类做作业,在其中,我必须使用 Java 语法给出静态动态绑定的示例。

我了解基本概念,即静态绑定发生在编译时,动态绑定发生在运行时,但我无法弄清楚它们具体是如何工作的。

我在网上找到了一个静态绑定的例子,它给出了这个例子:

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);
}

这会打印“动物正在吃东西”,因为调用callEat使用静态绑定,但我不确定为什么这被认为是静态绑定。

到目前为止,我所见过的任何资料都无法以我可以理解的方式解释这一点。

4

9 回答 9

130

来自Javarevisited 博客文章

以下是静态绑定和动态绑定之间的一些重要区别:

  1. Java 中的静态绑定发生在编译时,而动态绑定发生在运行时。
  2. private,方法finalstatic变量使用静态绑定并由编译器绑定,而虚拟方法在运行时根据运行时对象绑定。
  3. 静态绑定使用Typeclass在 Java 中)信息进行绑定,而动态绑定使用对象来解析绑定。
  4. 重载的方法使用静态绑定进行绑定,而重写的方法在运行时使用动态绑定进行绑定。

这是一个示例,它将帮助您理解 Java 中的静态和动态绑定。

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;
    }
}

输出:内部集合排序方法

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");
    }
}

输出: Car 的内部启动方法

于 2014-02-25T06:14:21.360 回答
24

将方法调用连接到方法主体称为绑定。正如 Maulik 所说,“静态绑定使用类型(Java 中的类)信息进行绑定,而动态绑定使用对象来解析绑定。” 所以这段代码:

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...");
    }
}

会产生结果:dog is eating...因为它正在使用对象引用来查找要使用的方法。如果我们把上面的代码改成这样:

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...");
    }
}

它会产生:animal is eating...因为它是一个静态方法,所以它使用 Type(在本例中为 Animal)来解析调用哪个静态方法。除了静态方法,私有方法和最终方法使用相同的方法。

于 2016-07-08T08:15:46.180 回答
11

那么为了了解静态和动态绑定的实际工作原理?或者编译器和JVM如何识别它们?

让我们看下面的例子,Mammal父类有一个方法speak()Human类 extends Mammal,覆盖该speak()方法,然后再次用 重载它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
    }
}

当我们编译上面的代码并尝试使用 来查看字节码javap -verbose OverridingInternalExample时,我们可以看到编译器生成了一个常量表,它为我提取并包含在程序本身中的程序的每个方法调用和字节码分配整数代码(请参阅每个方法调用下面的评论)

程序字节码

通过查看上面的代码,我们可以看到 , 和 的字节码humanMammal.speak()human.speak()完全human.speak("Hindi")不同的(invokevirtual #4, invokevirtual #7, invokevirtual #9),因为编译器能够根据参数列表和类引用来区分它们。因为所有这些都在编译时静态解决,所以方法重载被称为静态多态性静态绑定

anyMammal.speak()但是和的字节码humanMammal.speak()是相同的 ( invokevirtual #4) 因为根据编译器这两种方法都是在Mammal引用时调用的。

所以现在问题来了,如果两个方法调用具有相同的字节码,那么 JVM 是如何知道调用哪个方法的呢?

好吧,答案隐藏在字节码本身中,它是invokevirtual指令集。JVM 使用该invokevirtual指令调用与 C++ 虚拟方法等效的 Java。在 C++ 中,如果我们想覆盖另一个类中的一个方法,我们需要将其声明为虚拟,但在 Java 中,所有方法默认都是虚拟的,因为我们可以覆盖子类中的每个方法(私有、最终和静态方法除外)。

在 Java 中,每个引用变量都包含两个隐藏指针

  1. 指向再次保存对象方法的表的指针和指向 Class 对象的指针。例如 [speak(), speak(String) 类对象]
  2. 指向在堆上为该对象的数据分配的内存的指针,例如实例变量的值。

因此,所有对象引用都间接持有对包含该对象的所有方法引用的表的引用。Java 从 C++ 中借用了这个概念,这个表被称为虚拟表(vtable)。

vtable 是一个类似数组的结构,其中包含虚拟方法名称及其对数组索引的引用。JVM 在将类加载到内存时只为每个类创建一个 vtable。

因此,每当 JVM 遇到invokevirtual指令集时,它都会检查该类的 vtable 中的方法引用并调用特定方法,在我们的例子中,该方法是来自对象而不是引用的方法。

因为所有这些都只在运行时解决,并且在运行时 JVM 知道要调用哪个方法,这就是为什么Method Overriding被称为Dynamic Polymorphism或简称为PolymorphismDynamic Binding的原因。

您可以在我的文章How Does JVM Handle Method Overloading and Overriding Internally中阅读更多详细信息。

于 2019-01-18T11:14:05.647 回答
4

编译器只知道“a”的类型是Animal; 这发生在编译时,因此称为静态绑定(方法重载)。但如果它是动态绑定,那么它会调用Dog类方法。这是一个动态绑定的例子。

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");
    }
}

输出: Dog 的内部吃法

于 2014-03-05T10:33:54.097 回答
2

在设计编译器以及如何将变量过程传输到运行时环境时,静态绑定和动态绑定之间存在三个主要区别。这些差异如下:

静态绑定:在静态绑定中讨论了以下三个问题:

  • 程序的定义

  • 名称声明(变量等)

  • 声明范围

动态绑定:动态绑定中遇到的三个问题如下:

  • 激活程序

  • 名称绑定

  • 绑定的生命周期

于 2017-06-20T04:09:22.357 回答
1

使用父子类中的静态方法:静态绑定

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

动态绑定:

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
于 2018-07-02T05:59:03.260 回答
0

这里的所有答案都是正确的,但我想补充一些缺失的东西。当您覆盖静态方法时,看起来我们正在覆盖它,但实际上它不是方法覆盖。相反,它被称为方法隐藏。Java中不能覆盖静态方法。

看下面的例子:

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...");
    }
}

在动态绑定中,方法的调用取决于引用的类型,而不是引用变量所持有的对象的类型。这里发生静态绑定,因为方法隐藏不是动态多态性。如果您删除eat() 前面的static 关键字并使其成为非静态方法,那么它将向您显示动态多态性而不是隐藏方法。

我找到了以下链接来支持我的回答: https ://youtu.be/tNgZpn7AeP0

于 2019-12-19T07:09:58.560 回答
0

在编译时确定对象的静态绑定类型的情况下,而在运行时确定对象的动态绑定类型。



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();
    }

}
于 2020-04-11T11:48:36.973 回答
-3

因为编译器在编译时就知道绑定。例如,如果您在接口上调用方法,则编译器无法知道并且绑定在运行时被解析,因为在其上调用方法的实际对象可能是几个之一。因此,这是运行时或动态绑定。

您的调用在编译时绑定到 Animal 类,因为您已经指定了类型。如果您将该变量传递给其他地方的另一个方法,那么没有人会知道(除了您,因为您编写了它)它将是什么实际类。唯一的线索是声明的动物类型。

于 2013-09-26T00:56:13.303 回答