具有默认方法的接口何时初始化?

在搜索 Java 语言规范以回答 这个问题时,我学习了 那个

在初始化一个类之前,它的直接超类必须是 初始化为 < strong > ,但类实现的接口不是 类似地,接口的超级接口不是 在接口初始化之前初始化。

出于我自己的好奇心,我尝试了一下,正如预期的那样,接口 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

但是,如果接口声明了一个 default方法,那么初始化就会发生

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初始化器被执行。这意味着接口已初始化。

我在 JLS 中找不到任何迹象表明会发生这种情况。不要误解我的意思,我知道如果实现类没有为方法提供实现,那么应该会发生这种情况,但是如果它提供了实现呢?Java 语言规范中是否遗漏了这个条件,我是否遗漏了什么,或者我是否错误地解释了它?

9144 次浏览

The interface is not initialized because the constant field InterfaceType.init , which is being initialized by non constant value (method call), is not used anywhere.

It is known at compile time that constant field of interface is not used anywhere, and the interface is not containing any default method (In java-8) so there is no need to initialize or load the interface.

Interface will be initialized in following cases,

  • constant field is used in your code.
  • Interface contains a default method (Java 8)

In case of Default Methods, You are implementing InterfaceType. So, If InterfaceType will contain any default methods, It will be INHERITED (used) in implementing class. And Initialization will be into the picture.

But, If you are accessing constant field of interface (which is initialized in normal way), The interface initialization is not required.

Consider following code.

public class Example {
public static void main(String[] args) throws Exception {
InterfaceType foo = new InterfaceTypeImpl();
System.out.println(InterfaceType.init);
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();
}

In above case, Interface will be initialized and loaded because you are using the field InterfaceType.init.

I am not giving the default method example as you already given that in your question.

Java language specification and example is given in JLS 12.4.1 (Example does not contain default methods.)


I can not find JLS for Default methods, there may be two possibilities

  • Java people forgot to consider the case of default method. (Specification Doc bug.)
  • They just refer the default methods as non-constant member of interface. (But mentioned no where, again Specification Doc bug.)

This is a very interesting issue!

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;
}
}

Its expected output is:

1
j=3
jj=4
3

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!
}

the output changes to:

1
ii=2
j=3
jj=4
3

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.

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.

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.

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.

I'll try to make a case that an interface initialization should not cause any side-channel side effects that the subtypes depend on, therefore, whether this is a bug or not, or whichever way the Java fixes it, it should not matter to the application in which order interfaces are initialized.

In the case of a class, it is well accepted that it can cause side effects that subclasses depend on. For example

class Foo{
static{
Bank.deposit($1000);
...

Any subclass of Foo would expect that they'll see $1000 in the bank, anywhere in the subclass code. Therefore the superclass is initialized prior to the subclass.

Shouldn't we do the same thing for superintefaces as well? Unfortunately, the order of superinterfaces are not supposed to be significant, therefore there is no well defined order in which to initialize them.

So we better not establish this kind of side effects in interface initializations. After all, interface is not meant for these features (static fields/methods) we pile on for convenience.

Therefore if we follow that principle, it'll be no concern to us in which order interfaces are initialized.

The instanceKlass.cpp file from the OpenJDK contains the initialization method InstanceKlass::initialize_impl that corresponds to the Detailed Initialization Procedure in the JLS, which is analogously found in the Initialization section in the JVM Spec.

It contains a new step that is not mentioned in the JLS and not in the JVM book that is referred to in the code:

// refer to the JVM book page 47 for description of steps
...


if (this_oop->has_default_methods()) {
// Step 7.5: initialize any interfaces which have default methods
for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
Klass* iface = this_oop->local_interfaces()->at(i);
InstanceKlass* ik = InstanceKlass::cast(iface);
if (ik->has_default_methods() && ik->should_be_initialized()) {
ik->initialize(THREAD);
....
}
}
}

So this initialization has been implemented explicitly as a new Step 7.5. This indicates that this implementation followed some specification, but it seems that the written specification on the website has not been updated accordingly.

EDIT: As a reference, the commit (from October 2012!) where the respective step has been included in the implementation: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2: Coincidentally, I found this Document about default methods in hotspot which contains an interesting side note at the end:

3.7 Miscellaneous

Because interfaces now have bytecode in them, we must initialize them at the time that an implementing class is initialized.