静态比Java中的动态绑定

时间:2013-09-26 00:15:58

标签: java dynamic-binding static-binding

我正在为我的一个类做一个作业,在其中,我必须使用Java语法提供 static 动态绑定的示例。

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

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

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的调用使用静态绑定,但我不确定为什么这是被认为是静态绑定。

到目前为止,我所看到的所有来源都没有设法以我能够遵循的方式解释这一点。

9 个答案:

答案 0 :(得分:89)

来自Javarevisited blog post

  

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

     
      
  1. Java中的静态绑定在编译期间发生,而动态绑定在运行时发生。
  2.   
  3. privatefinalstatic方法和变量使用静态绑定,并且在运行时根据运行时对象绑定虚拟方法时由编译器绑定。
  4.   
  5. 静态绑定使用Type(Java中的class)信息进行绑定,而动态绑定使用object来解析绑定。
  6.   
  7. 使用静态绑定绑定重载方法,而在运行时使用动态绑定绑定重写方法。
  8.         

    这是一个帮助您了解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的内部启动方法

答案 1 :(得分:11)

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

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

会产生结果:狗正在吃... 因为它正在使用对象引用来查找要使用的方法。如果我们将上面的代码更改为:

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

它会产生:动物正在吃...... 因为它是静态方法,所以它使用Type(在本例中为Animal)来解析要调用的静态方法。除静态方法外,私有方法和最终方法使用相同的方法。

答案 2 :(得分:3)

编译器只知道“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");
    }
}

输出: 里面吃狗的方法

答案 3 :(得分:2)

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

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

  • 程序的定义

  • 声明名称(变量等)

  • 声明范围

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

  • 激活程序

  • 名称的绑定

  • 绑定的生命周期

答案 4 :(得分:1)

在父类和子类中使用static方法:静态绑定

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

答案 5 :(得分:1)

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

下面举一个例子,其中Mammal是具有方法speak()的父类,而Human类扩展了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查看字节码时,我们可以看到编译器生成了一个常数表,其中它为我拥有的程序的每个方法调用和字节码分配整数代码提取并包含在程序本身中(请参见每个方法调用下方的注释)

Program Bytecode

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

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

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

好吧,答案隐藏在字节码本身中,并且是Mammal指令集。 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 (动态多态) 多态动态绑定

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

答案 6 :(得分: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

答案 7 :(得分: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();
    }

}

答案 8 :(得分:-3)

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

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