Java 中的静态与动态绑定

我当前正在为我的一个类做作业,在作业中,我必须用 Java 语法给出 staticdynamic binding的示例。

我理解静态绑定发生在编译时,动态绑定发生在运行时的基本概念,但我不知道它们实际上是如何工作的。

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

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

So far none of the sources I've seen have managed to explain this in a way that I can follow.

179205 次浏览

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

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

来自 浏览过的博客文章:

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

  1. Java 中的静态绑定发生在编译时,而动态绑定发生在运行时。
  2. privatefinalstatic方法和变量使用静态绑定,由编译器绑定,而虚方法在运行时基于运行时对象绑定。
  3. Static binding uses Type (class in Java) information for binding while dynamic binding uses object to resolve binding.
  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;
}
}

输出 : Inside Collection 排序方法

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

输出: 车内启动方式

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

Output: 狗内食法

将方法调用连接到方法体称为 Binding。正如 Maulik 所说: “静态绑定使用 Type (Class in 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)来解析要调用的静态方法。除了静态方法,私有方法和最终方法使用相同的方法。

在设计编译器时,静态绑定和动态绑定以及如何将 变量程序转移到 runtime环境有三个主要区别。 These differences are as follows:

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

  • 过程的定义

  • Declaration of a name(variable, etc.)

  • 声明的范围

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

  • 启动程序

  • 名称的绑定

  • 绑定的生命周期

使用父类和子类中的 Static 方法: StaticBinding

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

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

Let's take below example where Mammal is a parent class which has a method speak() and Human class extends Mammal, overrides the speak() method and then again overloads it with 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 #7invokevirtual #9) ,因为编译器能够根据参数列表和类引用区分它们。因为所有这些都是在编译时静态解析的,这就是为什么 方法超载被称为 静态多态性静态绑定的原因。

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

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

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

In Java, every reference variable holds two hidden pointers

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

So all object references indirectly hold a reference to a table which holds all the method references of that object. Java has borrowed this concept from C++ and this table is known as virtual table (vtable).

Vtable 是一种类似数组的结构,它在数组索引上保存虚方法名称及其引用。当 JVM 将类加载到内存中时,每个类只创建一个 vtable。

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

因为所有这些问题只能在运行时解决,并且在运行时 JVM 知道调用哪个方法,这就是为什么 方法重写被称为 动态多态性或者简单地称为 多态性Dynamic Binding

你可以在我的文章 How Does JVM Handle Method Overloading and Overriding Internally上阅读更多的细节。

这里所有的答案都是正确的,但是我想补充一些遗漏的东西。 当您重写一个静态方法时,看起来我们正在重写它,但实际上它不是方法重写。相反,它被称为方法隐藏。在 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

In the case of the static binding type of object determined at the compile-time whereas in the dynamic binding type of the object is determined at the runtime.




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


}