web-dev-qa-db-ja.com

既定のメソッドを持つインターフェイスはいつ初期化されますか?

Java Language Specificationに答えて この質問 を検索している間に、私は that

クラスを初期化する前に、その直接スーパークラスを初期化する必要がありますが、クラスによって実装されるインターフェースは初期化されません。同様に、インターフェースのスーパーインターフェースは初期化されませんインターフェイスが初期化される前に初期化されます。

私自身の好奇心のために、私はそれを試してみましたが、予想どおり、インターフェイス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 )、初期化されるタイプのstatic初期化子が実行されます。これは、インターフェイスが初期化されたことを意味します。

これが起こるべきであることを示すものをJLSで見つけることができませんでした。誤解しないでください、実装クラスがメソッドの実装を提供しない場合にこれが起こるはずだと理解していますが、もしそうならどうでしょうか? Java Language Specificationからこの条件が欠落していますか、何かを見落としていましたか、それとも間違って解釈していますか?

92

これは非常に興味深い問題です!

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呼び出しのクリティカルパスに追加することを避けたいと考えていました。 Java 8およびデフォルトのメソッドの前では、invokevirtualはインターフェースでコードを実行することはできなかったため、これは発生しませんでした。これはクラス/インターフェイス準備段階( JLS 12.3.2 )では、メソッドテーブルなどを初期化します。

OpenJDK compiler-devメーリングリストで この質問を提起 しました。 Alex Buckleyからの返信 (JLSの編集者)がいて、JVMおよびラムダ実装チームに向けられたより多くの質問を提起しました。彼はまた、Tがインターフェースである場合、「Tはクラスであり、Tによって宣言された静的メソッドが呼び出される」という仕様にもバグがあることを指摘しています。そのため、ここには仕様とHotSpotの両方のバグがある可能性があります。

Disclosure:OpenJDKのOracleで働いています。人々がこれがこの質問に恩恵をもたらすことで私に不公平な利点を与えると思うなら、私はそれについて柔軟になりたいと思っています。

81
Stuart Marks

非定数値(メソッド呼び出し)で初期化されている定数フィールドInterfaceType.initはどこでも使用されないため、インターフェイスは初期化されません。

コンパイル時に、インターフェイスの定数フィールドはどこでも使用されず、インターフェイスにはデフォルトメソッド(Java-8)が含まれないため、インターフェイスを初期化またはロードする必要はありません。

インターフェイスは次の場合に初期化されます。

  • 定数フィールドはコードで使用されます。
  • インターフェースにはデフォルトのメソッドが含まれます(Java 8)

Default Methodsの場合、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();
}

上記の場合、フィールドInterfaceType.initを使用しているため、インターフェースが初期化されてロードされます。

既に質問であなたがそれを与えたので、私はデフォルトの方法の例を与えていません。

Java言語の仕様と例は JLS 12.4.1 に記載されています(例にはデフォルトのメソッドは含まれていません。)


DefaultメソッドのJLSが見つかりません。2つの可能性があります。

  • Javaの人々は、デフォルトメソッドのケースを考慮するのを忘れていました。 (仕様ドキュメントのバグ。)
  • これらは、デフォルトのメソッドをインターフェイスの非定数メンバーとして参照するだけです。 (ただし、どこにも言及していません。再び、仕様ドキュメントのバグです。)
13
Not a bug

OpenJDKの instanceKlass.cpp ファイルには、初期化メソッドInstanceKlass::initialize_implは、JLSの 初期化手順の詳細 に対応します。これは、JVM仕様の 初期化 セクションに類似しています。

これには、コードで参照されるJVMブックではなく、JLSで言及されていない新しいステップが含まれています。

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

そのため、この初期化はexplicitlyとして新しいステップ7.5として実装されました。これは、この実装が何らかの仕様に従っていることを示していますが、Webサイト上の仕様書はそれに応じて更新されていないようです。

編集:参照として、それぞれのステップが実装に含まれているコミット(2012年10月から!) http://hg.openjdk.Java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2:偶然にも、私はこれを見つけました ホットスポットのデフォルトのメソッドに関するドキュメント これは最後に興味深いサイドノートを含んでいます:

3.7その他

インターフェイスにはバイトコードが含まれるようになったため、実装クラスが初期化されるときにそれらを初期化する必要があります。

10
Marco13

インターフェースの初期化によって、サブタイプが依存するサイドチャネルの副作用が発生しないように、したがって、これがバグであるかどうか、またはJava =これを修正します。インターフェースが初期化される順序はアプリケーションにとって重要ではありません。

classの場合、サブクラスが依存する副作用を引き起こす可能性があることは十分に受け入れられています。例えば

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

Fooのサブクラスは、サブクラスコード内のどこにでも銀行に1000ドルが表示されることを期待します。したがって、スーパークラスはサブクラスの前に初期化されます。

スーパーインターフェイスでも同じことをすべきではないでしょうか?残念ながら、スーパーインターフェースの順序は重要ではないため、初期化する順序は明確に定義されていません。

したがって、インターフェイスの初期化でこの種の副作用を確立しない方がよいでしょう。結局のところ、interfaceはこれらの機能(静的フィールド/メソッド)のためのものではありません。

したがって、その原則に従えば、インターフェイスが初期化される順序は関係ありません。

1
ZhongYu