当前位置: 首页 > 知识库问答 >
问题:

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

牛枫
2023-03-14

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

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

出于我自己的好奇心,我尝试了它,正如预期的那样,接口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语言规范中是否缺少这个条件,我是否遗漏了什么,或者我是否错误地解释了它?

共有3个答案

爱刚捷
2023-03-14

Openhtml" target="_blank">JDK中的instanceKlass.cpp文件包含与JLS中的详细初始化过程相对应的初始化方法InstanceKlass::initialize_impl,这类似于JVM规范中的初始化部分。

它包含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杂项

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

习高格
2023-03-14

接口未初始化,因为常量字段InterfaceType。由非常量值(方法调用)初始化的init,不在任何地方使用。

编译时已知接口的常量字段不在任何地方使用,并且接口不包含任何默认方法(在java-8中),因此无需初始化或加载接口。

以下情况将初始化接口,

  • 常量字段用于代码中
  • 接口包含一个默认方法(Java8)

对于默认方法,您正在实现InterfaceType。所以,若InterfaceType将包含任何默认方法,那个么它将在实现类中被继承(使用)。初始化将进入图片中。

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

考虑下面的代码。

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

在上述情况下,将初始化并加载接口,因为您正在使用字段InterfaceType。初始化

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

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

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

  • Java人忘记考虑默认方法的情况。(规范Doc bug.)
  • 他们只是将默认方法称为接口的非常量成员。(但没有提到的地方,再次规范文档错误。)
桂飞翼
2023-03-14

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

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(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中添加了默认方法,

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调用的关键路径中。在Java8和默认方法之前,invokevirtual永远不会在接口中执行代码,因此不会出现这种情况。有人可能会认为这是类/接口准备阶段(JLS 12.3.2)的一部分,该阶段初始化方法表之类的东西。但可能这太过分了,不小心做了完全初始化。

我在OpenJDK编译器开发邮件列表中提出了这个问题。亚历克斯·巴克利(JLS的编辑)在回复中提出了更多针对JVM和lambda实现团队的问题。他还指出,这里的规范中有一个错误,它说“T是一个类,调用了由T声明的静态方法”,如果T是一个接口,也应该适用。因此,这里可能同时存在规范和HotSpot错误。

披露:我在OpenJDK上为Oracle工作。如果人们认为这给了我一个不公平的优势,让悬赏附加到这个问题上,我愿意灵活处理。

 类似资料:
  • 问题内容: 当搜寻通过Java语言规范来回答这个问题),我学到的是 在初始化类之前,必须先初始化其直接超类, 但不初始化由该类实现的接口。 同样,在初始化接口之前,不会初始化接口的超级接口。 出于我自己的好奇心,我尝试了一下,并且未如预期的那样对接口进行了初始化。 该程序打印 但是,如果接口声明了一个方法,则确实会发生初始化。考虑给定的接口 然后上面的相同程序将打印 换句话说,接口的字段已初始化(

  • 具有默认方法的接口将被初始化,即使重写了此方法,并且根本没有调用该方法。 示例: 印刷品: 这里到底有什么问题?

  • 问题内容: 如果未将显式值传递给方法,我想用一些默认值初始化方法的参数-像这样: 我得到了错误: 我该如何解决? 问题答案: 这里常见的习惯用法是将默认值设置为一些哨兵值用于此目的,这是典型值,然后您可以检查该值。 您可能还会看到用于哨兵的实例。 后一个版本的优点是可以传递给函数,但有一些缺点(请参见下面@larsmans的评论)。如果您不认为需要将有意义的参数传递给您的方法,那么我建议您使用它。

  • 考虑以下代码 VS2013编译器发出以下警告: 警告C4351:新行为:数组“B::member”的元素将默认初始化1 这里有记载 使用C 11,并应用“默认初始化”的概念,意味着B. part的元素将不会被初始化。 但我认为,成员{}应该执行值初始化,而不是默认初始化。VS2013编译器是否损坏? 8.5美元/6 默认初始化类型为T的对象意味着:-如果T是(可能是cv限定的)类类型(第9条),则

  • 我遇到了这个问题: 下面的输出是什么? 我的想法是: 我的第一直觉告诉我-i=new I(){}?因此,我们不能实例化接口-问题1。 那么我认为公共默认字符串是toString()?重写对象类方法?听起来不太好-问题2 可能的答案: a)10I b) 15I c)由于第11行编译失败 d)由于第15行编译失败 e)由于多个错误导致编译失败 解释完我的想法后,我选择了错误的答案。正确答案是D,我也答

  • 我在Visual Studio2019中有一个Xamarin表单解决方案。我现在只使用Android版本。完整的错误是: 在此进程中,java.lang.IllegalStateException Message=Default FirebaseApp未初始化AppCardView.Sample.android。确保首先调用FirebaseApp.InitializeApp(上下文)。 在我的An