什么时候有一个默认方法初始化的接口?
在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
并不是指为了这些特性(静态字段/方法)。
因此,如果我们遵循这个原则,那么我们就不用担心接口初始化的顺序。