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

时间:2018-03-31 08:21:07

标签: java bytecode

自Java 8以来,允许在接口中预定义方法。其中一些标准实现已经在诸如CharSequence之类的“标准”接口上实现。如果您尝试使用JVM 7读取Java 8 ByteCode(例如Java主文件夹的rt.jar),则会发生错误。

e.g。无法解析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
}

这个Bbytecode是由这个Javacode制作的:

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

所以这是我的问题:默认界面如何影响常量池?

这些标志是否相关:

  

CONSTANT_MethodHandle CONSTANT_MethodType CONSTANT_InvokeDynamic

3 个答案:

答案 0 :(得分:5)

采取以下示例:

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()。从字节码的角度来看,这也是新的东西,因为之前你只会通过super调用指向类(父类)的方法,而不是接口。

答案 1 :(得分:1)

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

  

如果您尝试使用JVM 7读取Java 8 ByteCode(例如Java主文件夹的rt.jar),则会发生错误。

     

e.g。无法解析java.lang.CharSequence类型。

但是这甚至与您尝试“使用JVM 7读取Java 8 ByteCode”时会发生的情况远远不匹配。如果您尝试使用Java 7 JVM加载Java 8类,通常会得到VerifyError,并显示一条消息,告知您不支持类文件版本。

相反,错误消息看起来非常类似于Eclipse编译器在读取类文件的字节代码时失败的错误消息,这与JVM无关。正如this answer中所解释的那样,Eclipse似乎没有在它找不到的类或它无法解析的类之间产生差异,只是说“无法解析”。它似乎也忽略了类文件的版本号,因此当它们不使用default方法等新功能时,它恰好适用于较新的类文件。

在技术层面上,差异很小。 default方法与常量池之间没有关系。池条目类型CONSTANT_MethodHandleCONSTANT_MethodTypeCONSTANT_InvokeDynamicdefault方法无关,它们甚至不是新的 - 自Java 7以来它们已经是标准的一部分(这并不能阻止一些工具供应商忽视它们,只要它们没有遇到它们)。 default方法只是一种非abstract而非static的方法,就像普通的public实例方法一样,但在接口中。新的事情是,现在允许。一个简单的类文件解析器,它不关心它是否正在读classinterface将没有任何困难。但是,根据工具对数据的作用,它可能会遇到这样的类文件(如果它还没有停止在版本号上),就像旧的Eclipse编译器一样。

如果JVM的验证程序尚未停止在版本号上,它只会抛出一个不同的VerifierError,表示类文件违反了所有方法都必须abstract的约束。

答案 2 :(得分:0)

Anurag已经解释了一下默认方法的实现,但我想解决你问题中的另一个问题:

  

如果您尝试阅读Java 8 ByteCode(例如Java home的rt.jar)   文件夹)与JVM 7,发生错误。

     

e.g。无法解析java.lang.CharSequence类型。

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

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