99

プロジェクトにいくつかのクラスが含まれており、それぞれに静的初期化ブロックがあるとします。これらのブロックはどのような順序で実行されますか? クラス内では、そのようなブロックはコードに表示される順序で実行されることを知っています。クラス間で同じだと読んだことがありますが、私が書いたいくつかのサンプルコードはそれに同意しません。私はこのコードを使用しました:

package pkg;

public class LoadTest {
    public static void main(String[] args) {
        System.out.println("START");
        new Child();
        System.out.println("END");
    }
}

class Parent extends Grandparent {
    // Instance init block
    {
        System.out.println("instance - parent");
    }

    // Constructor
    public Parent() {
        System.out.println("constructor - parent");
    }

    // Static init block
    static {
        System.out.println("static - parent");
    }
}

class Grandparent {
    // Static init block
    static {
        System.out.println("static - grandparent");
    }

    // Instance init block
    {
        System.out.println("instance - grandparent");
    }

    // Constructor
    public Grandparent() {
        System.out.println("constructor - grandparent");
    }
}

class Child extends Parent {
    // Constructor
    public Child() {
        System.out.println("constructor - child");
    }

    // Static init block
    static {
        System.out.println("static - child");
    }

    // Instance init block
    {
        System.out.println("instance - child");
    }
}

そして、この出力を得ました:

START
静的 - 祖父母
静的 - 親
静的 - 子
インスタンス - 祖父母
コンストラクター - 祖父母
インスタンス - 親
コンストラクター - 親
インスタンス - 子
コンストラクター - 子
END

そこから明らかな答えは、親のブロックが子のブロックよりも先に実行されるということですが、これは偶然の一致である可能性があり、2 つのクラスが同じ階層にない場合は役に立ちません。

編集:

これを LoadTest.java に追加して、サンプル コードを変更しました。

class IAmAClassThatIsNeverUsed {
    // Constructor
    public IAmAClassThatIsNeverUsed() {
        System.out.println("constructor - IAACTINU");
    }

    // Instance init block
    {
        System.out.println("instance - IAACTINU");
    }

    // Static init block
    static {
        System.out.println("static - IAACTINU");
    }
}

クラス名が示すように、新しいクラスをどこにも参照していません。新しいプログラムは、古いプログラムと同じ出力を生成しました。

4

8 に答える 8

95

JLS バージョン 8のセクション 12.4 および 12.5 を参照してください。これらすべてについて詳細に説明されています (静的変数の場合は 12.4、インスタンス変数の場合は 12.5)。

静的初期化の場合 (セクション 12.4):

クラスまたはインターフェイスの型 T は、次のいずれかが最初に発生する直前に初期化されます。

  • T はクラスであり、T のインスタンスが作成されます。
  • T はクラスであり、T によって宣言された静的メソッドが呼び出されます。
  • T によって宣言された static フィールドが割り当てられます。
  • T によって宣言された静的フィールドが使用され、そのフィールドは定数変数ではありません (§4.12.4)。
  • T は最上位クラス (§7.6) であり、T (§8.1.3) 内に字句的にネストされた assert ステートメント (§14.10) が実行されます。

(およびいくつかのイタチ語句)

于 2010-01-05T17:07:18.647 に答える
65

クラスの静的初期化子は、インスタンスを作成するため、または静的メソッドまたはフィールドにアクセスするために、クラスが最初にアクセスされたときに実行されます。

したがって、複数のクラスの場合、これはそれらのクラスをロードするために実行されるコードに完全に依存します。

于 2010-01-05T17:05:01.710 に答える
36

キースとクリスの答えはどちらも素晴らしいです。特定の質問に詳細を追加しているだけです。

静的 init ブロックは、クラスが初期化された順序で実行されます。 では、その順番は?JLS 12.4.1 あたり:

クラスまたはインターフェイスの型 T は、次のいずれかが最初に発生する直前に初期化されます。

  • T はクラスであり、T のインスタンスが作成されます。
  • T はクラスであり、T によって宣言された静的メソッドが呼び出されます。
  • T によって宣言された static フィールドが割り当てられます。
  • T によって宣言された静的フィールドが使用され、そのフィールドは定数変数ではありません (§4.12.4)。
  • T は最上位クラスであり、T 内でレキシカルにネストされた assert ステートメント (§14.10) が実行されます。

クラス Class およびパッケージ java.lang.reflect で特定のリフレクト メソッドを呼び出すと、クラスまたはインターフェイスの初期化も発生します。クラスまたはインターフェイスは、他の状況では初期化されません。

説明のために、例で何が起こっているかのウォークスルーを次に示します。

  1. メインに入る
  2. 「開始」を印刷
  3. Child の初期化が必要な Child の最初のインスタンスを作成しようとしています
  4. 子を初期化しようとすると、親が初期化されます
  5. 親を初期化しようとすると、祖父母が初期化されます
  6. 祖父母の初期化の開始時に、祖父母の静的初期化ブロックが実行されます
  7. 技術的には、オブジェクトは祖父母の親であることにより、初期化チェーンで最後の発言権を取得しますが、貢献するものは何もありません
  8. 祖父母の静的初期化ブロックが終了した後、プログラムは親の静的初期化ブロックにフォールバックします
  9. 親の静的初期化ブロックが終了した後、プログラムは子の静的初期化ブロックにフォールバックします
  10. この時点で、Child が初期化されるため、そのコンストラクターは処理を続行できます。
  11. IAmAClassThatIsNeverUsed は参照されないため、静的初期化ブロックを含め、そのコードは実行されません
  12. このチュートリアルの残りの部分は、静的初期化子には関係なく、完全を期すためにのみ含まれています
  13. 子のコンストラクターは暗黙的に super() を呼び出します (つまり、親のコンストラクター)
  14. 親のコンストラクターは暗黙的に super() を呼び出します (つまり、祖父母のコンストラクター)
  15. 祖父母のコンストラクターも同じことを行いますが、これは効果がありません (ここでも、Object は何も貢献しません)。
  16. 祖父母のコンストラクターが super() を呼び出した直後に、祖父母のインスタンス初期化ブロックが来る
  17. 祖父母の残りのコンストラクターのコンストラクターが実行され、コンストラクターが終了します
  18. プログラムは、super() への呼び出し (つまり、祖父母のコンストラクター) が解決された直後に、親のコンストラクターにフォールバックします。
  19. 上記のように、親のインスタンス初期化子がその役割を果たし、そのコンストラクターが終了します
  20. 同様に、プログラムは Child のコンストラクターに戻って完了します。
  21. この時点で、オブジェクトはインスタンス化されています
  22. 「終了」を出力
  23. 正常終了
于 2010-01-05T18:38:51.250 に答える
1

静的ブロックが呼び出されないケースが 1 つあります。

class Super {
    public static int i=10;
}
class Sub extends Super {
    static {
        system.out.println("Static block called");
    }
}
class Test {
    public static void main (String [] args) {
        system.out.println(Sub.i);
    } 
}

上記のコード出力10


「編集者」からの更新

これに関する技術的な説明はJLS 12.4.1にあります。

「静的フィールド (§8.3.1.1) への参照は、サブクラス、サブインターフェイス、またはインターフェイスを実装するクラスの名前を介して参照される場合でも、実際にそれを宣言するクラスまたはインターフェイスのみの初期化を引き起こします。 ."

直感的な説明はSuper.i、 とSub.iは実際には同じ変数であり、が正しい値を取得するSubために実際に初期化する必要があるものは何もありません。Super.i

Super.i(初期化式がクラスを参照する場合は異なりSubます。しかし、その場合、初期化順序に循環があります。JLS 12.4.1JLS 12.4.2を注意深く読むと、これが許可されていることが説明されており、実際に何が起こるかを正確に計算してください。)

于 2016-11-17T09:50:11.287 に答える
0

したがって、同じクラスに複数の静的初期化子とインスタンス初期化子を含めることができます。

  • 静的初期化子は、宣言されたテキストの順序で呼び出されます ( 12.4.2以降)
  • インスタンス初期化子は、宣言されたテキストの順序で呼び出されます ( 12.5以降)

それぞれが単一のブロックであるかのように実行されます。

于 2014-02-25T18:21:58.063 に答える