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_MethodHandle
、CONSTANT_MethodType
和 CONSTANT_InvokeDynamic
与 default
方法无关,它们甚至不是新的——它们已经是标准的一部分,因为 Java 7(这不会阻止一些工具供应商忽略它们,只要他们没有遇到它们)。 default
方法只是一种不是 abstract
也不是 static
的方法,就像普通的 public
实例方法一样,但是在接口中。新的事情是现在允许。一个简单的 class 文件解析器,它不关心它正在读取 class
还是 interface
将没有困难。但是,根据工具对数据的处理方式,它可能会遇到这样的 class 文件(如果它还没有停止在版本号处),就像旧的 Eclipse 编译器一样。
如果 JVM 的验证器没有停止在版本号上,它会简单地抛出一个不同的 VerifierError
说 class 文件违反了所有方法必须是 [=18] 的约束=].
从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_MethodHandle
、CONSTANT_MethodType
和 CONSTANT_InvokeDynamic
与 default
方法无关,它们甚至不是新的——它们已经是标准的一部分,因为 Java 7(这不会阻止一些工具供应商忽略它们,只要他们没有遇到它们)。 default
方法只是一种不是 abstract
也不是 static
的方法,就像普通的 public
实例方法一样,但是在接口中。新的事情是现在允许。一个简单的 class 文件解析器,它不关心它正在读取 class
还是 interface
将没有困难。但是,根据工具对数据的处理方式,它可能会遇到这样的 class 文件(如果它还没有停止在版本号处),就像旧的 Eclipse 编译器一样。
如果 JVM 的验证器没有停止在版本号上,它会简单地抛出一个不同的 VerifierError
说 class 文件违反了所有方法必须是 [=18] 的约束=].