[英]Static Vs. Dynamic Binding in Java
我目前正在为我的一个课程做作业,在其中,我必须使用 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
的调用使用了静态绑定,但我不确定为什么这被认为是静态绑定。
到目前为止,我所看到的所有消息来源都无法以我可以遵循的方式解释这一点。
以下是静态和动态绑定之间的一些重要区别:
- Java 中的静态绑定发生在编译时,而动态绑定发生在运行时。
private
、final
和static
方法和变量使用静态绑定并由编译器绑定,而虚拟方法在运行时基于运行时对象绑定。- 静态绑定使用
Type
(Java 中的class
)信息进行绑定,而动态绑定使用对象来解析绑定。- 重载的方法使用静态绑定绑定,而重写的方法在运行时使用动态绑定绑定。
下面是一个示例,它将帮助您理解 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 的内部启动方法
将方法调用连接到方法主体称为绑定。 正如 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 eat...因为它正在使用对象引用来查找要使用的方法。 如果我们把上面的代码改成这样:
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 eat...因为它是一个静态方法,所以它使用 Type(在本例中为 Animal)来解析要调用的静态方法。 除了静态方法私有和最终方法使用相同的方法。
那么为了了解静态和动态绑定实际上是如何工作的? 或者编译器和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 #4
, invokevirtual #7
, invokevirtual #9
)因为编译器能够根据参数列表和类引用来区分它们。 因为所有这些都在编译时静态解决,这就是方法重载被称为静态多态或静态绑定的原因。
但是anyMammal.speak()
和humanMammal.speak()
字节码是相同的( humanMammal.speak()
invokevirtual #4
),因为根据编译器,这两种方法都是在Mammal
引用上调用的。
所以现在问题来了,如果两个方法调用都有相同的字节码,那么 JVM 怎么知道调用哪个方法呢?
好吧,答案隐藏在字节码本身中,它是invokevirtual
指令集。 JVM 使用invokevirtual
指令来调用 Java 等效的 C++ 虚拟方法。 在 C++ 中,如果我们想覆盖另一个类中的一个方法,我们需要将其声明为虚拟的,但在 Java 中,默认情况下所有方法都是虚拟的,因为我们可以覆盖子类中的每个方法(私有、最终和静态方法除外)。
在 Java 中,每个引用变量都包含两个隐藏的指针
因此,所有对象引用都间接地持有对表的引用,该表持有该对象的所有方法引用。 Java 从 C++ 借用了这个概念,这个表被称为虚拟表(vtable)。
vtable 是一个类似数组的结构,它保存虚拟方法名称及其对数组索引的引用。 JVM 在将类加载到内存中时,为每个类只创建一个 vtable。
因此,每当 JVM 遇到invokevirtual
指令集时,它都会检查该类的 vtable 以获取方法引用并调用特定方法,在我们的例子中是来自对象的方法而不是引用。
因为所有这些都只在运行时得到解决,并且在运行时 JVM 知道要调用哪个方法,这就是方法覆盖被称为动态多态或简称为多态或动态绑定的原因。
您可以在我的文章“JVM 如何在内部处理方法重载和覆盖”中阅读更多详细信息。
编译器只知道“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里面的eat方法
在设计编译器以及如何将变量和过程传输到运行时环境时,静态和动态绑定之间存在三个主要区别。 这些差异如下:
静态绑定:在静态绑定中讨论了以下三个问题:
程序的定义
名称的声明(变量等)
申报范围
动态绑定:动态绑定中遇到的三个问题如下:
程序的激活
名称的绑定
绑定的生命周期
使用父子类中的静态方法:静态绑定
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
这里的所有答案都是正确的,但我想添加一些缺失的东西。 当您覆盖静态方法时,看起来我们正在覆盖它,但实际上它不是方法覆盖。 相反,它被称为方法隐藏。 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
在编译时确定对象的静态绑定类型的情况下,而在运行时确定对象的动态绑定类型的情况下。
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();
}
}
因为编译器在编译时就知道绑定。 例如,如果您在接口上调用一个方法,那么编译器无法知道并且绑定在运行时解析,因为在其上调用方法的实际对象可能是多个对象之一。 因此,这是运行时或动态绑定。
您的调用在编译时绑定到 Animal 类,因为您已经指定了类型。 如果您将该变量传递给其他地方的另一个方法,则没有人知道(除了您,因为您编写了它)它将是什么实际类。 唯一的线索是声明的 Animal 类型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.