返回

解剖 Java 方法调用(下):对象方法动态决议

IOS

动态方法决议(Dynamic Method Resolution)

在上一篇文章中,我们讨论了 Java 方法调用的慢速查找流程。当编译器无法根据静态类型来确定要调用的方法时,就会触发慢速查找流程。

在慢速查找流程中,JVM 会从对象的实际类型开始,沿继承链向上查找,直到找到一个具有该方法的类。如果在整个继承链中都没有找到该方法,则会抛出 NoSuchMethodError 异常。

动态方法决resolution,在运行时,根据对象实例的实际类型来确定要执行的方法,确保满足多态和动态分派的需要。

让我们从字节码指令层面一步步跟踪方法查找的过程,以更深入地理解动态方法决议是如何工作的。

字节码指令:invokevirtual

当遇到对象方法调用时,Java 虚拟机会执行 invokevirtual 字节码指令。该指令有两个操作数:

  • 对象引用:要调用方法的对象的引用
  • 方法名称和符:要调用的方法的名称和符

例如,以下字节码指令调用了 java.lang.String 类中的 length() 方法:

0: aload_1
1: invokevirtual #2 <java/lang/String.length: ()I>

其中:

  • aload_1 指令将第一个方法参数(对象引用)加载到操作数栈中
  • invokevirtual 指令调用 java.lang.String.length() 方法,并将方法的返回值(方法的返回值类型为 int)压入操作数栈中

方法查找过程

invokevirtual 指令执行时,JVM 会根据以下步骤查找要调用的方法:

  1. 从对象的实际类型开始,查找具有该方法的类
  2. 如果在当前类中没有找到该方法,则沿继承链向上查找,直到找到一个具有该方法的类
  3. 如果在整个继承链中都没有找到该方法,则会抛出 NoSuchMethodError 异常

例如,假设我们有一个 Animal 类和一个 Dog 类,Dog 类继承了 Animal 类。Animal 类有一个 speak() 方法,Dog 类重写了 speak() 方法。

当我们调用 Dog 对象的 speak() 方法时,JVM 会先在 Dog 类中查找该方法。如果在 Dog 类中没有找到,则会沿继承链向上查找,直到找到 Animal 类。在 Animal 类中找到了 speak() 方法,因此 JVM 会调用 Animal 类的 speak() 方法。

多态与动态分派

多态(polymorphism)是指能够根据对象的实际类型来调用相应的方法。动态分派(dynamic dispatch)是指在运行时根据对象的实际类型来确定要执行的方法。

在 Java 中,多态和动态分派是通过动态方法决议来实现的。当调用对象方法时,JVM 会根据对象的实际类型来查找要调用的方法,从而实现多态和动态分派。

重写与重载

重写(overriding)是指子类重新定义父类的方法。重载(overloading)是指在同一个类中定义多个具有相同名称但参数不同的方法。

重写和重载都会影响方法查找过程。当调用重写的方法时,JVM 会在子类中查找该方法,而不会在父类中查找。当调用重载的方法时,JVM 会根据方法的参数来确定要调用的方法。

总结

动态方法决议是 Java 语言的一项重要特性,它允许实现多态和动态分派。通过理解动态方法决议的原理,我们可以更好地理解 Java 方法调用的机制。