具有默认方法的接口何时初始化? [英] When is an interface with a default method initialized?

查看:14
本文介绍了具有默认方法的接口何时初始化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在搜索 Java 语言规范以回答这个问题时,我了解到

While searching through the Java Language Specification to answer this question, I learned 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.

出于我自己的好奇,我尝试了它,并且正如预期的那样,接口 InterfaceType 没有初始化.

For my own curiosity, I tried it and, as expected, the interface InterfaceType was not initialized.

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

然而,如果接口声明了一个 default 方法,那么初始化确实会发生.考虑给定为

However, if the interface declares a default method, then initialization does occur. Consider the InterfaceType interface given as

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

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

然后上面的程序会打印

static initializer  
implemented method

也就是说接口的static字段被初始化(详细初始化过程中的第 9 步) 并执行正在初始化的类型的 static 初始化程序.这意味着接口已初始化.

In other words, the static field of the interface is initialized (step 9 in the Detailed Initialization Procedure) and the static initializer of the type being initialized is executed. This means that the interface was initialized.

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

I could not find anything in the JLS to indicate that this should happen. Don't get me wrong, I understand that this should happen in case the implementing class doesn't provide an implementation for the method, but what if it does? Is this condition missing from the Java Language Specification, did I miss something, or am I interpreting it wrongly?

推荐答案

这是一个很有趣的问题!

This is a very interesting issue!

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

It seems like JLS section 12.4.1 ought to cover this definitively. However, the behavior of Oracle JDK and OpenJDK (javac and HotSpot) differs from what's specified here. In particular, the Example 12.4.1-3 from this section covers interface initialization. The example as follows:

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

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

and indeed I get the expected output. However, if a default method is added to 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

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

which clearly indicates that interface I is being initialized where it wasn't before! The mere presence of the default method is enough to trigger the initialization. The default method doesn't have to be called or overridden or even mentioned, nor does the presence of an abstract method trigger initialization.

我的推测是 HotSpot 实现想要避免将类/接口初始化检查添加到 invokevirtual 调用的关键路径中.在 Java 8 和默认方法之前,invokevirtual 永远不会在接口中执行代码,所以没有出现这种情况.有人可能认为这是类/接口准备阶段的一部分(JLS 12.3.2) 初始化方法表之类的东西.但也许这太过分了,而是意外地进行了完全初始化.

My speculation is that the HotSpot implementation wanted to avoid adding class/interface initialization checking into the critical path of the invokevirtual call. Prior to Java 8 and default methods, invokevirtual could never end up executing code in an interface, so this didn't arise. One might think this is part of the class/interface preparation stage (JLS 12.3.2) which initializes things like method tables. But perhaps this went too far and accidentally did full initialization instead.

提出了这个问题在 OpenJDK 编译器开发邮件列表中.Alex Buckley 的回复(编辑在 JLS 中,他提出了更多针对 JVM 和 lambda 实现团队的问题.他还指出,这里的规范中有一个错误,其中说T 是一个类并且调用了 T 声明的静态方法",如果 T 是一个接口,也应该适用.所以,这里可能存在规范和 HotSpot 错误.

I've raised this question on the OpenJDK compiler-dev mailing list. There's been a reply from Alex Buckley (editor of the JLS) in which he raises more questions directed at the JVM and lambda implementation teams. He also notes that there's a bug in the spec here where it says "T is a class and a static method declared by T is invoked" should also apply if T is an interface. So, it might be that there are both specification and HotSpot bugs here.

披露:我在 OpenJDK 上为 Oracle 工作.如果人们认为这给了我一个不公平的优势来获得附加到这个问题的赏金,我愿意对此保持灵活.

Disclosure: I work for Oracle on OpenJDK. If people think this gives me an unfair advantage at getting the bounty attached to this question, I'm willing to be flexible about it.

这篇关于具有默认方法的接口何时初始化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆