什么时候有一个默认方法初始化的接口?

在searchJava语言规范来回答这个问题时 ,我了解到了这一点

在一个类被初始化之前,它的直接超类必须被初始化, 但是这个类实现的接口没有被初始化。 类似地,接口的超级接口在初始化之前不会被初始化。

为了我自己的好奇心,我尝试了它,并且如预期的那样, 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方法,那么初始化就会发生。 考虑一下给出的InterfaceType接口

 interface InterfaceType { public static final ClassInitializer init = new ClassInitializer(); public default void method() { System.out.println("default method"); } } 

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

 static initializer implemented method 

换句话说,接口的static字段被初始化( 详细初始化过程中的步骤9 ),并执行被初始化types的static初始化器。 这意味着界面已经初始化了。

我在JLS中找不到任何东西来表明这种情况应该发生。 不要误解我的意思,我知道如果实现类没有提供方法的实现,会发生这种情况,但是如果这样做呢? Java语言规范中缺less这个条件吗,我错过了什么,或者我错误地解释了它?

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

JLS第12.4.1节似乎应该明确地加以说明。 但是,Oracle JDK和OpenJDK(javac和HotSpot)的行为与此处指定的不同。 特别是,本节中的示例12.4.1-3涵盖了接口初始化。 示例如下:

 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(Ji); System.out.println(Kj); } static int out(String s, int i) { System.out.println(s + "=" + i); return i; } } 

其预期产出是:

 1 j=3 jj=4 3 

实际上我得到了预期的产出。 但是,如果将缺省方法添加到接口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正在被初始化的地方不是以前! 仅仅存在默认方法就足以触发初始化。 缺省方法不必被调用或覆盖,甚至不提及,抽象方法的存在也不会触发初始化。

我的推测是,HotSpot实现希望避免将类/接口初始化检查添加到invokevirtual调用的关键path中。 在使用Java 8和缺省方法之前, invokevirtual永远不会在一个接口中执行代码,所以不会出现这种情况。 有人可能会认为这是类/接口准备阶段( JLS 12.3.2 )的一部分,它将方法表等东西初始化。 但是,也许这太过分了,意外做了全面的初始化。

我在OpenJDK编译器开发邮件列表中提出了这个问题 。 Alex Buckley (JLS的编辑)已经给出了一个答复 ,他提出了针对JVM和lambda实现团队的更多问题。 他还指出,如果T是一个接口,那么说明“T是一个类,T声明的一个静态方法被调用”的规范中有一个错误。 所以,这可能是因为规范和HotSpot的错误。

披露 :我在OpenJDK上为Oracle工作。 如果人们认为这样做给我带来这个问题的不公平优势,我愿意为此而灵活。

该接口没有被初始化,因为常量字段InterfaceType.init被非常量值(方法调用)初始化,没有在任何地方使用。

在编译时知道接口的常量字段没有在任何地方使用,接口也没有包含任何默认方法(在java-8中),所以不需要初始化或加载接口。

界面将在以下情况下被初始化,

  • 常量字段用于您的代码。
  • 接口包含一个默认方法(Java 8)

缺省方法的情况下,您正在实现InterfaceType 。 所以,如果InterfaceType将包含任何默认的方法,它将在实现类中被INHERITED(使用) 。 并初始化将成为图片。

但是,如果正在访问接口的常量字段(以正常方式初始化),则不需要接口初始化。

考虑下面的代码。

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

在上面的例子中,Interface将被初始化并加载,因为你使用的是InterfaceType.init字段。

我没有给你默认的方法的例子,因为你已经给你的问题。

Java语言规范和示例在JLS 12.4.1中给出(示例不包含默认方法)。


我找不到默认的JLS方法,可能有两种可能

  • Java的人忘记考虑默认方法的情况。 (规范文档错误。)
  • 他们只是将默认方法称为接口的非常量成员。 (但提到没有在哪里,再次规范Doc错误。)

OpenJDK中的instanceKlass.cpp文件包含初始化方法InstanceKlass::initialize_impl ,它对应于JLS中的详细初始化过程 ,类似于JVM Spec中的初始化部分。

它包含JLS中没有提到的新步骤,而不是代码中提到的JVM书中提到的步骤:

 // 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); .... } } } 

所以这个初始化已经被明确地实现为一个新的步骤7.5 。 这表明这个实现遵循一些规范,但似乎网站上的书面规范没有相应更新。

编辑:作为参考,提交(从2012年10月!)其中相应的步骤已被列入实施: http : //hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

编辑2:巧合的是,我发现这个文件有关热点的默认方法,最后包含一个有趣的方面说明:

3.7杂项

因为接口现在已经有字节码了,所以我们必须在实现类初始化的时候初始化它们。

我将尝试使接口初始化不应该引起子types所依赖的任何副通道副作用,因此,无论这是否是一个错误,或者Java修复它的任何方式,它应该无关紧要订单接口初始化的应用程序。

在一个class的情况下,它可以引起子类依赖的副作用被广泛接受。 例如

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

Foo类都期望在子类代码中的任何位置都能看到$ 1000的银行。 因此,超类在子类之前被初始化。

我们不应该为superintefaces做同样的事吗? 不幸的是,超级接口的顺序不应该是重要的,因此没有明确的顺序来初始化它们。

所以我们最好不要在界面初始化中build立这种副作用。 毕竟,为了方便起见, interface并不是指为了这些特性(静态字段/方法)。

因此,如果我们遵循这个原则,那么我们就不用担心接口初始化的顺序。