静态与 Java中的动态绑定


103

我目前正在为我的一个类进行分配,在其中,我必须使用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);
}

而且这将显示“ animal is eating”,因为对的调用callEat使用了静态绑定,但是我不确定为什么将其视为静态绑定。

到目前为止,我所见过的任何资料都没有设法以我可以遵循的方式来解释这一点。



1
注意,有几种不同的概念称为“绑定”。在这种特殊情况下,对于这种类型的绑定(涉及在相似但不完全相同的方法“签名”之间进行选择),编译器(做出“静态”决定,因为它们在运行时不会改变)决定该参数是“动物”,因为这是变量“ a”的(静态)类型。
热门点击2013年

(在某些语言中,对特定方法签名的选择将保留到运行时为止,并且将选择callEat(Dog)。)
Hot Licks

Answers:


114

Javarevisited博客文章

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

  1. Java中的静态绑定发生在编译时,而动态绑定发生在运行时。
  2. privatefinal以及static方法和变量使用静态结合和由编译器所键合而虚拟方法基于运行时对象在运行期间接合。
  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");
    }
}

输出:汽车内部启动方式



11
我仍然不了解其中的区别,
technazi '16

9
@technazi静态绑定仅查看类型(等于之前的任何值,例如Collection c = new HashSet();因此实际上它是哈希集时,它将被视为只是一个集合对象)。动态绑定考虑了实际对象(等于之后的内容,因此它实际上识别出其HashSet)。
标记

22

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

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)来解析要调用的静态方法。除了静态方法,私有方法和最终方法都使用相同的方法。


1
Java为什么不能在编译时推断出a实际上是一个Dog
胡志明市NGHIA

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

输出:狗的内食法


这样会不会引发诸如“无法从静态上下文引用非静态类/方法”之类的编译错误?考虑到main是静态的,我总是对此感到困惑。提前致谢。
Amnor

3

那么为了了解静态和动态绑定实际上是如何工作的呢?或如何通过编译器和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,我们可以看到编译器生成了一个常量表,其中它为我提取并包含在程序本身中的程序的每个方法调用和字节码分配整数代码(参见下面每个方法调用的注释)

程序字节码

通过看上面的代码中我们可以看到的字节码humanMammal.speak()human.speak()并且human.speak("Hindi")是完全不同的(invokevirtual #4invokevirtual #7invokevirtual #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知道要调用的方法,所以这就是为什么方法覆盖被称为Dynamic Polymorphism或简称为PolymorphismDynamic Binding的原因

您可以在我的文章JVM如何内部处理方法重载和替代中阅读更多详细信息。


2

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

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

  • 程序的定义

  • 名称声明(变量等)

  • 申报范围

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

  • 激活程序

  • 绑定名称

  • 绑定的生命周期


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

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


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

}

-3

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

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


1
不对。如果您在接口上进行调用,则编译器将做出完全相同的决定。
Hot Licks

@HotLicks与什么完全一样的决定?如果编译一个类以在接口上调用foo(String str)方法,则编译器无法在编译时知道应在哪个类上调用foo(String str)方法。仅在运行时,方法调用才能绑定到特定的类实现。
亚伦2013年

但是仍然会发生对特定方法签名的静态绑定。编译器仍然会选择callEat(Animal)而不是callEat(Dog)。
热门点击2013年

@HotLicks当然可以,但这不是我回答的问题。也许这对我造成了误解:DI将它与在接口上进行调用进行了比较,以突出显示在编译时编译器无法知道您是否实际实例化了另一个子类/实现。
亚伦

实际上,在编译时,编译器可以很容易地(在这种情况下)知道“ a”是一个Dog。实际上,它可能不得不花些时间才能“忘记”这一点。
热门点击2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.