为什么JVM在解析非接口方法时会考虑超接口?(JVM 5.4.3.3)

Mat*_*s17 6 java jvm

JVM 规范 ( 5.4.3.3 ) 描述了如何为方法引用完成方法解析。如果在类或其超类中找不到方法,它会尝试在超接口中找到方法。

这是什么原因?由超接口声明的方法不会作为接口方法 ref 而不是方法 ref 列在常量池中吗?

我的理解是方法引用用于invokevirtual操作,而接口方法引用用于invokeinterface操作。我不知道如何使用invokevirtual <methodref>.

Joh*_*uhn 5

为什么不?

你可以调用.stream()一个ArrayList<>就好了。事实上,下面的片段

ArrayList<Object> arr = new ArrayList<>();
arr.stream();
Run Code Online (Sandbox Code Playgroud)

将被编译为

     0: new           #16                 // class java/util/ArrayList
     3: dup
     4: invokespecial #18                 // Method java/util/ArrayList."<init>":()V
     7: astore_1
     8: aload_1
     9: invokevirtual #19                 // Method java/util/ArrayList.stream:()Ljava/util/stream/Stream;
Run Code Online (Sandbox Code Playgroud)

但是ArrayList<>(或任何它的超类)没有.stream()方法。
使用的方法实现来自interface Collection

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果在任何时候ArrayList<>决定它可以提供更好的.stream()方法,那么您就不想再次编译代码。
此外,该.stream()方法的实现(或非实现)会泄漏,最好避免这种情况。

  • @MattDs17 你如何在编译时知道运行时类是否会覆盖该方法?这是 Java 的一个基本原则,编译器将搜索继承的方法只是为了验证方法调用并获取正确的签名,但会对编译时接收器类型进行编码。JLS §13 列出了许多允许在不破坏已编译类的情况下进行的更改。Methodref 和 InterfaceMethodref 之间的区别已经变得不像过去那么有意义,请参阅[此答案](/sf/answers/2405322741/)。 (3认同)
  • 据我记得,java的早期版本确实会查找谁拥有像“super.toString()”这样的调用方法并记录下来。例如,如果我们有“class MyClass extends MySuper”,但“MySuper”没有覆盖“.toString()”,则“Object.toString”将被记录为“invokespecial”指令的目标。但是,当“MySuper”更改并且新版本覆盖“.toString()”时,早期的 java 版本将继续调用“Object.toString()”。为了缓解这种情况,引入了“ACC_SUPER”。 (3认同)
  • 确实是@JohannesKuhn。这允许手工制作的字节码跳过重写方法。该行为在版本 1.0.2 中已更改。但到了 Java 8,对 1.0.2 之前的类的向后兼容性就被放弃了。“java.lang.Object”类的“final”方法有一个例外(“getClass()”、“wait()”、“ notify()`) 是使用 java.lang.Object 作为接收器类型进行编译的。 (2认同)