java - 何时初始化具有默认方法的接口(interface)?

标签 java interface java-8 default-method

在搜索 Java 语言规范以回答 this question 时,我学会了that

Before a class is initialized, its direct superclass must be initialized, but interfaces implemented by the class are not initialized. Similarly, the superinterfaces of an interface are not initialized before the interface is initialized.

出于自己的好奇,我试了一下,果然没有初始化接口(interface)InterfaceType

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

这个程序打印

implemented method

但是,如果接口(interface)声明了一个 default 方法,那么就会发生初始化。考虑给出的 InterfaceType 接口(interface)

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

那么上面的程序就会打印出来

static initializer  
implemented method

换句话说,接口(interface)的 static 字段被初始化( step 9 in the Detailed Initialization Procedure )并且被初始化类型的 static 初始化器被执行。这意味着接口(interface)已初始化。

我在 JLS 中找不到任何表明应该发生这种情况的内容。不要误会我的意思,我知道这应该发生在实现类没有为该方法提供实现的情况下,但如果它提供了怎么办? Java 语言规范中是否缺少此条件,是我遗漏了什么,还是我解释错误?

最佳答案

这是一个非常有趣的问题!

好像是 JLS section 12.4.1应该明确地涵盖这一点。但是,Oracle JDK 和 OpenJDK(javac 和 HotSpot)的行为与此处指定的不同。特别是,本节中的示例 12.4.1-3 涵盖了接口(interface)初始化。示例如下:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

它的预期输出是:

1
j=3
jj=4
3

确实我得到了预期的输出。但是,如果在接口(interface)I中添加了默认方法,

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

输出变为:

1
ii=2
j=3
jj=4
3

这清楚地表明接口(interface) I 正在初始化,它不是以前的!仅存在默认方法就足以触发初始化。默认方法不必被调用或覆盖甚至提及,抽象方法的存在也不会触发初始化。

我的猜测是,HotSpot 实现希望避免将类/接口(interface)初始化检查添加到 invokevirtual 调用的关键路径中。在 Java 8 和默认方法之前,invokevirtual 永远不会在接口(interface)中执行代码,因此不会出现这种情况。有人可能认为这是类/接口(interface)准备阶段 (JLS 12.3.2) 的一部分,它初始化方法表之类的东西。但也许这太过分了,不小心做了完整的初始化。

我有 raised this question在 OpenJDK 编译器开发邮件列表中。有一个reply from Alex Buckley (JLS 的编辑),他在其中提出了更多针对 JVM 和 lambda 实现团队的问题。他还指出,这里的规范中有一个错误,其中说“T 是一个类,并且调用了 T 声明的静态方法”如果 T 是一个接口(interface)也应该适用。因此,这里可能同时存在规范和 HotSpot 错误。

披露:我在 OpenJDK 上为 Oracle 工作。如果人们认为这给了我一个不公平的优势来获得这个问题的赏金,我愿意对此采取灵活态度。

关于java - 何时初始化具有默认方法的接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23096084/

相关文章:

java - RequestDispatcher 到同一包中的 JSP

java - 显示堆栈内容时出现问题

c# - 查找接口(interface)实例背后的具体类型

Java 正则表达式元字符在拆分时返回额外的空间

java - 泛型友好的类型处理程序映射

java - 如何从 map 中生成具有不同值的 map (并使用 BinaryOperator 使用正确的键)?

java - 使用正则表达式从字符串中删除一些额外的文本

JavaFX8.我是否必须为每个 FXMLLoader 设置资源包?

java - 关于两个线程的同步

java - Spring 数据: how to use repository's inner interfaces outside the outer class?