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


94

この質問に答えるために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メソッドを宣言する場合は、初期化が行われます。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言語仕様にありませんか、何か見落としましたか、それとも間違って解釈しますか?


4
私の推測は-そのようなインターフェイスは、初期化順序の観点から抽象クラスと見なされます。これが正しい発言かどうかわからないので、コメントとしてこれを書きました:)
Alexey Malev 14

JLSのセクション12.4にあるはずですが、そこにはないようです。欠けていると思います。
Warren Dew

1
ネヴァーマインド....彼らは理解していないか、彼らはdownvoteになる説明を持っていないほとんどの時間は:( .Thisは、SO一般的に起こる。
NeverGiveUp161

interfaceはJavaでは具体的なメソッドを定義すべきではないと考えました。InterfaceTypeコードがコンパイルされたことに驚きました。
MaxZoom

回答:


85

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

それはのように思える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の両方のバグがある可能性があります。

開示:私はOpenJDKのOracleで働いています。これがこの質問に賞金を結びつけることで不当な利点を私に与えると人々が思うなら、私はそれについて柔軟に進んでいきます。


6
私は公式の情報源を求めました。これ以上公式にならないと思います。すべての展開を確認す​​るには、2日かかります。
Sotirios Delimanolis 14

48
@StuartMarks「これが私に不当な利点などを与えると人々が思う場合」=>私たちはここに質問への回答を得るためにここにいて、これは完璧な答えです!
アッシリア14

2
サイドノート:JVM仕様は、JLSの場合と同様である記述が含まれていますdocs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5これは十分のように更新する必要があります。
Marco13 14

2
@assyliasとSotirios、コメントありがとうございます。彼らは、アッシリアのコメントに対する14本の賛成票(この記事の執筆時点)とともに、不公平の可能性についての私の懸念を軽減しました。
スチュアートマークス

1
@SotiriosDelimanolis JDK-8043275JDK-8043190に関連すると思われるいくつかのバグがあり、それらは8u40で修正済みとマークされています。ただし、動作は同じようです。これにはいくつかのJVM仕様の変更も絡み合っているため、おそらく修正は「古い初期化順序に戻す」以外の何かです。
スチュアートマークス

13

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

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

インターフェースは以下の場合に初期化されます、

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

デフォルトメソッドの場合は、実装し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.init

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

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


デフォルトメソッドのJLSが見つかりません。2つの可能性があります。

  • Javaの人々は、デフォルトの方法の場合を考慮することを忘れていました。(仕様ドキュメントのバグ。)
  • これらは、デフォルトのメソッドをインターフェースの非定数メンバーとして参照するだけです。(しかし、仕様書に関するバグはどこにもありませんでした。)

デフォルトのメソッドのリファレンスを探しています。フィールドは、インターフェースが初期化されたかどうかを示すためだけのものでした。
Sotirios Delimanolis 14

@SotiriosDelimanolis私はデフォルトの方法の答えで理由を述べました...しかし残念ながら、デフォルトの方法ではまだJLSが見つかりません。
バグではない

残念ながら、それは私が探しているものです。あなたの答えは、私が質問ですでに述べたことを単に繰り返すように感じます。defaultメソッドが含まれ、インターフェースを実装するクラスが初期化されている場合、インターフェースが初期化されること。
Sotirios Delimanolis 2014

私はJavaの人々がデフォルトのメソッドのケースを考慮するのを忘れたと思うか、またはデフォルトのメソッドをインターフェースの非定数メンバーとして参照するだけです(私の仮定、どのドキュメントにも見つかりません)。
バグではない

1
@KishanSarsechaGajjar:インターフェイスの非定数フィールドとはどういう意味ですか?インターフェースの変数/フィールドは、デフォルトで静的なfinalです。
ロケシュ2014

10

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

これには、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

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

3.7その他

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


1
これを掘り下げてくれてありがとう。(+1)新しい「ステップ7.5」が誤って仕様から省略されたか、提案されて拒否され、実装が修正されて削除されなかった可能性があります。
スチュアートマークス

1

インターフェイスの初期化では、サブタイプが依存するサイドチャネルの副作用を引き起こさないようにするため、これがバグかどうか、またはJavaが修正する方法に関係なく、注文インターフェイスが初期化されるアプリケーション。

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

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

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.