在 Java bytecode/class 格式中,什么决定了一个方法是否覆盖了另一个?
In the Java bytecode/class format, what determines if a method overrides another?
我知道字节码规范允许 classes 具有相同签名的方法,仅在 return 类型上有所不同,与 Java 语言不同。有些语言甚至在某些情况下使用它。我的问题与反思有关:
if 在 class 中,我找到一个(非私有)方法,其名称和参数类型与(非最终的,非私有的)其中一个 superclass 相同,并且具有return 类型等于 superclass 中所述方法的 return 类型的子类型,我什么时候可以假设静态调用 'supermethod' 的代码将 总是导致'overriding(?)'方法的执行(自然地假设调用是在那个class的对象上进行的)?即使在编译为 JVM 字节码的其他语言的情况下,或者如果涉及运行时代码生成,或者在像 lambda 转发器这样的黑客合成 classes 中?
我的问题是注意到 Scala 标准库中的 Iterable[E]
有一个方法:
def map[O](f :E => O) :Iterable[E]
虽然 Map[K, V]
,Iterable[(K, V)]
的子 class 声明了另一个方法:
def map[A, B](f :((K, V)) => (A, B)) :Map[A, B]
实际签名比这里复杂,但原理是一样的:擦除后,Map
中的方法可以覆盖Iterable
中的方法。
这最终取决于所使用的 JVM 指令:
invokespecial
会调用该方法,而不根据当前对象的类型进行动态解析。
invokevirtual
将根据 class.
进行调度
相关:Why invokeSpecial is needed when invokeVirtual exists
所以答案取决于生成的字节码。
重写的事实由 JVM 通过方法描述符(包括参数类型 和 和 return 类型)的完全相等来确定。请参见 JVMS §5.4.5, §5.4.6。
因此,在 JVM 级别,方法 returning Map
不会覆盖方法 returning Iterable
。编译器通常会生成一个桥接方法 returning Iterable
,通过对方法 returning Map
.
的简单委托来实现
示例:
class A<T extends Iterable> {
T map() {
return null;
}
}
class B extends A<Collection> {
@Override
Collection map() {
return null;
}
}
此处 B.map
覆盖 A.map
,即使 return 类型不同。为了使这种层次结构成为可能和有效,编译器生成(在字节码级别)另一个方法 B.map
即 returns Iterable
:
class B extends A<java.util.Collection> {
B();
0: aload_0
1: invokespecial #1 // Method A."<init>":()V
4: return
java.util.Collection map();
0: aconst_null
1: areturn
java.lang.Iterable map();
0: aload_0
1: invokevirtual #7 // Method map:()Ljava/util/Collection;
4: areturn
当在 B
的实例上调用虚方法 A.map
时,总是会调用方法 B.map:Iterable
,后者又会调用 B.map:Collection
.
我知道字节码规范允许 classes 具有相同签名的方法,仅在 return 类型上有所不同,与 Java 语言不同。有些语言甚至在某些情况下使用它。我的问题与反思有关:
if 在 class 中,我找到一个(非私有)方法,其名称和参数类型与(非最终的,非私有的)其中一个 superclass 相同,并且具有return 类型等于 superclass 中所述方法的 return 类型的子类型,我什么时候可以假设静态调用 'supermethod' 的代码将 总是导致'overriding(?)'方法的执行(自然地假设调用是在那个class的对象上进行的)?即使在编译为 JVM 字节码的其他语言的情况下,或者如果涉及运行时代码生成,或者在像 lambda 转发器这样的黑客合成 classes 中?
我的问题是注意到 Scala 标准库中的 Iterable[E]
有一个方法:
def map[O](f :E => O) :Iterable[E]
虽然 Map[K, V]
,Iterable[(K, V)]
的子 class 声明了另一个方法:
def map[A, B](f :((K, V)) => (A, B)) :Map[A, B]
实际签名比这里复杂,但原理是一样的:擦除后,Map
中的方法可以覆盖Iterable
中的方法。
这最终取决于所使用的 JVM 指令:
invokespecial
会调用该方法,而不根据当前对象的类型进行动态解析。
进行调度invokevirtual
将根据 class.
相关:Why invokeSpecial is needed when invokeVirtual exists
所以答案取决于生成的字节码。
重写的事实由 JVM 通过方法描述符(包括参数类型 和 和 return 类型)的完全相等来确定。请参见 JVMS §5.4.5, §5.4.6。
因此,在 JVM 级别,方法 returning Map
不会覆盖方法 returning Iterable
。编译器通常会生成一个桥接方法 returning Iterable
,通过对方法 returning Map
.
示例:
class A<T extends Iterable> {
T map() {
return null;
}
}
class B extends A<Collection> {
@Override
Collection map() {
return null;
}
}
此处 B.map
覆盖 A.map
,即使 return 类型不同。为了使这种层次结构成为可能和有效,编译器生成(在字节码级别)另一个方法 B.map
即 returns Iterable
:
class B extends A<java.util.Collection> {
B();
0: aload_0
1: invokespecial #1 // Method A."<init>":()V
4: return
java.util.Collection map();
0: aconst_null
1: areturn
java.lang.Iterable map();
0: aload_0
1: invokevirtual #7 // Method map:()Ljava/util/Collection;
4: areturn
当在 B
的实例上调用虚方法 A.map
时,总是会调用方法 B.map:Iterable
,后者又会调用 B.map:Collection
.