代码之家  ›  专栏  ›  技术社区  ›  osanger

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

  •  7
  • osanger  · 技术社区  · 7 年前

    由于Java 8,允许在接口中预定义方法。其中一些标准实现已经在“标准”接口(如CharSequence)上实现。如果尝试使用JVM 7读取Java 8字节码(例如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\u MethodHandle CONSTANT\u MethodType CONSTANT\u InvokedDynamic

    3 回复  |  直到 7 年前
        1
  •  5
  •   Anurag Sharma    7 年前

    以以下示例为例:

    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调用方法。

        2
  •  1
  •   Community CDub    4 年前

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

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

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

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

    相反,当Eclipse编译器无法读取类文件的字节码时,错误消息看起来非常像众所周知的错误消息,而这与JVM无关。如中所述 this answer ,Eclipse似乎并没有在它没有找到的类和它未能解析的类之间做出区别,只是说 无法解决 。它似乎也忽略了类文件的版本号,所以当新的类文件没有使用新的功能时,它正好可以使用新的类文件,如 default 方法。

    在技术层面上,差异很小。两者之间没有关系 违约 方法和常量池。池条目类型 CONSTANT_MethodHandle ,则, CONSTANT_MethodType CONSTANT_InvokeDynamic 与…无关 违约 方法和它们甚至都不是新的,它们已经是自Java 7以来的标准的一部分(这不会阻止一些工具供应商忽略它们,只要他们没有遇到它们)。A. 违约 方法只是一种 abstract 而不是 static ,就像一个普通人 public 实例方法,但在接口中。新情况是 允许的 现在一个简单的类文件解析器,它不关心是否正在读取 class interface 不会有什么困难的。但是,根据该工具对数据的处理方式,它可能会像旧的Eclipse编译器一样处理这样的类文件(如果它还没有在版本号上停止的话)。

    如果JVM验证器没有在版本号处停止,它只会抛出一个不同的 VerifierError 说类文件违反了所有方法都必须 摘要

        3
  •  0
  •   Antimony    7 年前

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

    如果您尝试读取Java 8字节码(例如Java home的rt.jar 文件夹),则会发生错误。

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

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

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