在 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.