自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
答案 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_MethodHandle
,CONSTANT_MethodType
和CONSTANT_InvokeDynamic
与default
方法无关,它们甚至不是新的 - 自Java 7以来它们已经是标准的一部分(这并不能阻止一些工具供应商忽视它们,只要它们没有遇到它们)。 default
方法只是一种非abstract
而非static
的方法,就像普通的public
实例方法一样,但在接口中。新的事情是,现在允许。一个简单的类文件解析器,它不关心它是否正在读class
或interface
将没有任何困难。但是,根据工具对数据的作用,它可能会遇到这样的类文件(如果它还没有停止在版本号上),就像旧的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也会拒绝加载它。