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

醉折花枝作酒筹
リリース: 2021-08-04 17:49:46
転載
2072 人が閲覧しました

このような質問に遭遇したことのある学生は多いと思いますが、一度確認しただけで忘れてしまい、再度質問されても正しく答えることができません。次に、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 は静的コードによってトリガーされます。

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

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

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

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

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

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

5. 通常の実践

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

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

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

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

親クラス<clinit> > サブクラス<clinit> > ; 親クラス <init> > サブクラス <init>

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

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

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

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

結論

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

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

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

関連ラベル:
ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!