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からこの条件が欠落していますか、何かを見落としていましたか、それとも間違って解釈していますか?
これは非常に興味深い問題です!
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で働いています。人々がこれがこの質問に恩恵をもたらすことで私に不公平な利点を与えると思うなら、私はそれについて柔軟になりたいと思っています。
非定数値(メソッド呼び出し)で初期化されている定数フィールドInterfaceType.init
はどこでも使用されないため、インターフェイスは初期化されません。
コンパイル時に、インターフェイスの定数フィールドはどこでも使用されず、インターフェイスにはデフォルトメソッド(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つの可能性があります。
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その他
インターフェイスにはバイトコードが含まれるようになったため、実装クラスが初期化されるときにそれらを初期化する必要があります。
インターフェースの初期化によって、サブタイプが依存するサイドチャネルの副作用が発生しないように、したがって、これがバグであるかどうか、またはJava =これを修正します。インターフェースが初期化される順序はアプリケーションにとって重要ではありません。
class
の場合、サブクラスが依存する副作用を引き起こす可能性があることは十分に受け入れられています。例えば
class Foo{
static{
Bank.deposit($1000);
...
Foo
のサブクラスは、サブクラスコード内のどこにでも銀行に1000ドルが表示されることを期待します。したがって、スーパークラスはサブクラスの前に初期化されます。
スーパーインターフェイスでも同じことをすべきではないでしょうか?残念ながら、スーパーインターフェースの順序は重要ではないため、初期化する順序は明確に定義されていません。
したがって、インターフェイスの初期化でこの種の副作用を確立しない方がよいでしょう。結局のところ、interface
はこれらの機能(静的フィールド/メソッド)のためのものではありません。
したがって、その原則に従えば、インターフェイスが初期化される順序は関係ありません。