Java 8字节码接口中的默认方法是如何标记的

Java 8 bytecode how are default methods in interfaces marked

从Java 8开始,允许在接口中预定义方法。其中一些标准实现已经在 "standard" 接口上实现,例如 CharSequence。如果您尝试使用 JVM 7 读取 Java 8 字节码(例如 Java 主文件夹的 rt.jar),则会发生错误。

例如无法解析类型 java.lang.CharSequence。

这可能不是级别之间唯一的区别,但我想了解新字节码的结构。

public interface com.company.ITest {
public void HelloWorld();
Code:
   0: getstatic     #1                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: ldc           #2                  // String hello world
   5: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8: return
}

这个字节码是由这个Java代码产生的:

public interface ITest {
    default void HelloWorld(){
        System.out.println("hello world");
    }
}

所以这是我的问题:默认接口对常量池有何影响?

这些标志是否相关:

CONSTANT_MethodHandle CONSTANT_MethodType CONSTANT_InvokeDynamic

举个例子:

public interface A {
    default void foo(){
       System.out.println("Calling A.foo()");
    }
}

public class Clazz implements A {
}

从客户端代码的角度来看,默认方法只是普通的虚拟方法。因此得名——虚拟扩展方法。因此,对于调用默认方法的客户端代码的示例,将在调用站点生成 invokeinterface。

A clazz = new Clazz();
clazz.foo(); // invokeinterface foo()

Clazz clazz = new Clazz();
clazz.foo(); // invokevirtual foo()

在默认方法冲突解决的情况下,当我们重写默认方法并希望将调用委托给接口之一时,将推断 invokespecial 因为我们将专门调用实现:

public class Clazz implements A, B {
    public void foo(){
       A.super.foo(); // invokespecial foo()
    }
}

public void foo();
Code:
0: aload_0
1: invokespecial #2 // InterfaceMethod A.foo:()V
4: return

如您所见,invokespecial指令用于调用接口方法foo()。从字节码的角度来看,这也是新事物,因为以前您只能通过指向 class(父 class)而不是接口的 super 调用方法。

Anurag 已经解释了一些关于默认方法的实现,但我想解决您问题中的另一点:

If you try to read Java 8 ByteCode (e.g. the rt.jar of the Java home folder) with a JVM 7, an error occurs.

e.g. the type java.lang.CharSequence cannot be resolved.

这是因为每个类文件都有一个版本代码,代表编译它的 Java 版本。 JVM 将拒绝任何版本代码高于自身的类文件(为了向后兼容,较低的代码也可以)。

这意味着即使字节码没有任何其他变化,JVM 仍会拒绝从 Java 的未来版本加载类文件。您可以用 Java 10 编译 "hello world",而 Java 9 JVM 将拒绝加载它,即使没有新功能或字​​节码差异。

这取决于你实际在做什么,你会遇到什么障碍。你说

If you try to read Java 8 ByteCode (e.g. the rt.jar of the Java home folder) with a JVM 7, an error occurs.

e.g. the type java.lang.CharSequence cannot be resolved.

但这甚至与当您尝试“使用 JVM 7 读取 Java 8 字节码”时发生的情况完全不匹配。如果您尝试使用 Java 7 JVM 加载 Java 8 class,您通常会收到 VerifyError 消息,告诉您 class 文件版本不支持。

相反,该错误消息看起来很像 Eclipse 编译器在读取 class 文件的字节码失败时出现的众所周知的错误消息,这与 JVM 无关。正如 this answer 中所解释的,Eclipse 似乎没有区分 class 是它没有找到还是 class 是它解析失败,只是说“无法解决”。它似乎也忽略了 class 文件版本号,因此当它们不使用 default 方法等较新的功能时,它恰好适用于较新的 class 文件。

在技术层面上,差异很小。 default方法和常量池没有关系。池条目类型 CONSTANT_MethodHandleCONSTANT_MethodTypeCONSTANT_InvokeDynamicdefault 方法无关,它们甚至不是新的——它们已经是标准的一部分,因为 Java 7(这不会阻止一些工具供应商忽略它们,只要他们没有遇到它们)。 default 方法只是一种不是 abstract 也不是 static 的方法,就像普通的 public 实例方法一样,但是在接口中。新的事情是现在允许。一个简单的 class 文件解析器,它不关心它正在读取 class 还是 interface 将没有困难。但是,根据工具对数据的处理方式,它可能会遇到这样的 class 文件(如果它还没有停止在版本号处),就像旧的 Eclipse 编译器一样。

如果 JVM 的验证器没有停止在版本号上,它会简单地抛出一个不同的 VerifierError 说 class 文件违反了所有方法必须是 [=18] 的约束=].