ホームページ >Java >&#&チュートリアル >Javaのstaticキーワードを一気に理解しよう

Javaのstaticキーワードを一気に理解しよう

醉折花枝作酒筹
醉折花枝作酒筹転載
2021-08-04 17:49:462155ブラウズ

このような質問に遭遇したことのある学生は多いと思いますが、一度確認しただけで忘れてしまい、再度質問されても正しく答えることができません。次に、4 つのステップを通じて、このコードの実行シーケンスを分解し、ルールをまとめます。

最初の質問は、コードの実行順序を調べることです。

public class Parent {
    static {
        System.out.println("Parent static initial block");
    }

    {
        System.out.println("Parent initial block");
    }

    public Parent() {
        System.out.println("Parent constructor block");

    }
}

public class Child extends Parent {
    static {
        System.out.println("Child static initial block");
    }

    {
        System.out.println("Child initial block");
    }
    
    private Hobby hobby = new Hobby();

    public Child() {
        System.out.println("Child constructor block");
    }
}

public class Hobby {
    static{
        System.out.println("Hobby static initial block");
    }

    public Hobby() {
        System.out.println("hobby constructor block");
    }
}

new Child() が実行されると、上記のコードは何を出力しますか?

このような問題に遭遇したことのある学生は多いと思いますが、一度確認しただけで忘れてしまい、再び問題に遭遇しても正しく答えられないことがあります。次に、クラスの代表者が、このコードの実行シーケンスを分解し、ルールを要約するための 4 つのステップを説明します。

1. コンパイラーは何を最適化しますか?

次の 2 つのコードは、コンパイル前とコンパイル後の変更を比較します。

コンパイル前の Child.java

public class Child extends Parent {
    static {
        System.out.println("Child static initial block");
    }
    {
        System.out.println("Child initial block");
    }
    
    private Hobby hobby = new Hobby();
    
    public Child() {
        System.out.println("Child constructor block");
    }
}

コンパイル後の Child.class

public class Child extends Parent {
    private Hobby hobby;

    public Child() {
        System.out.println("Child initial block");
        this.hobby = new Hobby();
        System.out.println("Child constructor block");
    }

    static {
        System.out.println("Child static initial block");
    }
}

渡された 比較から、コンパイラが初期化ブロックとインスタンス フィールドの代入操作をコンストラクター コードの前に移動し、関連するコードの順序を保持していることがわかります。実際、複数のコンストラクターがある場合、初期化コードがコピーされて移動されます。

これに基づいて、最初の優先順位を導き出すことができます:

  • 初期化コード > コンストラクター コード

2. static は何をするのでしょうか?

クラスの読み込みプロセスは、大きく 3 つの段階に分けることができます: 読み込み -> リンク -> 初期化

初期化段階は 8 つの状況によってトリガーされます周志明》P359 "8クラスの初期化をトリガーするタイプ 状況 ") トリガー:

  • new キーワードを使用してオブジェクトをインスタンス化する場合

  • 静的フィールドを読み取るか設定しますa type (constant" )

  • 型の静的メソッドの呼び出し

  • リフレクションを使用してクラスを呼び出す場合

  • クラスを初期化するときに、親クラスが初期化されていないことが判明した場合、親クラスの初期化が最初にトリガーされます。

  • #仮想マシンの起動時、メイン クラス (main() を含む) メソッドが最初に初期化されます)

  • MethodHandle インスタンスが初めて呼び出されるとき、メソッドが指すクラスを初期化します。

  • インターフェイス内にデフォルト メソッド (デフォルトの変更されたインターフェイス メソッド) が定義されており、インターフェイスの実装クラスが初期化されている場合は、その前にインターフェイスを初期化する必要があります。

項目 2 と 3 は静的コードによってトリガーされます。

実際、初期化フェーズはクラス コンストラクター583d030be372af71281df966e84181a5 メソッドを実行するプロセスです。このメソッドはコンパイラによって自動的に生成され、static.statement ブロック (static{} ブロック) によって変更されたすべてのクラス変数の代入アクションと静的変更を収集し、これらのコードの出現順序を保持します。項目 5 では、JVM は、サブクラスの 583d030be372af71281df966e84181a5 メソッドが実行される前に、親クラスの 583d030be372af71281df966e84181a5 メソッドが実行されていることを確認します。クラスの初期化は、583d030be372af71281df966e84181a5 を実行すること、つまり、静的に変更された代入アクションと static{} ブロックを実行することです。JVM は、親クラスの初期化が最初に実行され、その後に実行されることを保証します。

これにより、優先順位が 2 番目になります:

親クラスの静的コード>サブクラスの静的コード

3 .static コードは 1 回だけ実行されます。
  • 静的コード (静的メソッドを除く) は 1 回だけ実行されることは誰もが知っています。
このメカニズムがどのように保証されるか考えたことはありませんか?

答えは: 親委任モデルです。

JDK8 以前の親委任モデルは次のとおりです:

アプリケーション クラス ロード デバイス → 拡張クラス ローダー → クラス ローダー開始

通常の開発で作成されたクラスは、デフォルトでアプリケーション クラス ローダーによってロードされ、その親クラスである拡張クラス ローダーに委任されます。拡張クラス ローダーは、その親クラスである起動クラス ローダーに委譲します。親クラス ローダーがロード要求を完了できないと報告した場合にのみ、子ローダーは独自にロードを完了しようとします。このプロセスが親の委任です。 3 つの間の親子関係は、継承ではなく、結合モードによって実現されます。

この処理の実装も非常にシンプルで、主要な実装コードは以下のとおりです:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    // 首先检查该类是否被加载过
    // 如果加载过,直接返回该类
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 如果父类抛出ClassNotFoundException
            // 说明父类无法完成加载请求
        }

        if (c == null) {
            // 如果父类无法加载,转由子类加载
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

コメントを付ければ誰でも簡単に理解できると思います。

親によって委任されたコードから、同じクラス ローダーでは、クラスは 1 回しかロードできず、初期化も 1 回のみに制限されていることがわかります。したがって、クラス内の静的コード (静的メソッドを除く) は、クラスが初期化されるときに 1 回だけ実行されます

4. 7e51f00a783d7eb8f68358439dee7daf および 583d030be372af71281df966e84181a5

前述したように、コンパイラは、クラス コンストラクター: 583d030be372af71281df966e84181a5 メソッドを自動的に生成します。このメソッドは、すべての静的に変更されたクラス変数の代入アクションと静的ステートメント ブロック (static{} ブロック) を収集し、コードの出現順序を保持します。クラスは初期化されます

これに応じて、コンパイラはインスタンス フィールドの代入アクション、初期化ステートメント ブロック ({} ブロック) のコード、およびコンストラクターを収集する 7e51f00a783d7eb8f68358439dee7daf メソッドも生成します。 (コンストラクター)、コードの出現順序を保持します。新しい命令の後に実行されます

したがって、クラスを新規作成するときに、JVM がクラスをロードしていない場合、クラスは初期化されます。最初にインスタンス化されます。

この時点で、3 番目に優先順位の高いルールを作成する準備が整いました:

  • 静的コード (static{} ブロック、静的フィールド割り当てステートメント) > 初期化コード ({} ブロック、インスタンス フィールド割り当てステートメント)

5. 通常の実践

前の 3 つのルールを結合して、次の 2 つを要約します:

1. 静的コード (静的{} ブロック、静的フィールド割り当てステートメント) > 初期化コード ({} ブロック、インスタンス フィールド割り当てステートメント) >コンストラクター コード

2. 親クラスの静的コード>サブクラスの静的コード

前の概要によると、初期化コードとコンストラクター コードはコンパイラー によって収集され、静的コードは 583d030be372af71281df966e84181a5 に収集されるため、上記のルールが再度マージされます:

親クラス583d030be372af71281df966e84181a5 > サブクラス583d030be372af71281df966e84181a5 > ; 親クラス 7e51f00a783d7eb8f68358439dee7daf > サブクラス 7e51f00a783d7eb8f68358439dee7daf

冒頭の質問に対応するので、練習してみましょう:

new Child() を実行するとき、new キーワードは Child クラスの初期化をトリガーします。JVM は親クラスがあることを検出すると、まず Parent クラスを初期化し、Parent クラスの 583d030be372af71281df966e84181a5 メソッドの実行を開始します。次に、Child クラスの 583d030be372af71281df966e84181a5 メソッドを実行します (583d030be372af71281df966e84181a5 で何が収集されるか覚えていますか?)。

次に、Child クラスのオブジェクトのインスタンス化を開始します。この時点で、Child の 7e51f00a783d7eb8f68358439dee7daf メソッドを実行する準備ができています。それには親クラスがあることがわかります。7e51f00a783d7eb8f68358439dee7daf メソッドを実行します最初に親クラスの ; メソッドを実行し、次に 7e51f00a783d7eb8f68358439dee7daf の子クラスを実行します (7e51f00a783d7eb8f68358439dee7daf で収集される内容を覚えていますか?)。

これを読めば、最初の質問に対する答えはすでに得ていると思います。最初に出力シーケンスを手書きしてから、コードを書いて自分で検証するのもよいでしょう。

結論

静的は日常の開発で頻繁に使用されます。私が書くたびに、常に 2 つの疑問が頭の中にあります。なぜ静的を使用する必要があるのか​​? 静的でなくても大丈夫ですか?

この記事からわかるように、静的アプリケーションは単なるクラス変数や静的メソッドをはるかに超えています。古典的なシングルトン パターンでは、static のさまざまな使い方が見られますが、次の記事では、シングルトン パターンを派手に記述する方法について書きます。

以上がJavaのstaticキーワードを一気に理解しようの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。