このような質問に遭遇したことのある学生は多いと思いますが、一度確認しただけで忘れてしまい、再度質問されても正しく答えることができません。次に、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 つのステップを説明します。
次の 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"); } }
渡された 比較から、コンパイラが初期化ブロックとインスタンス フィールドの代入操作をコンストラクター コードの前に移動し、関連するコードの順序を保持していることがわかります。実際、複数のコンストラクターがある場合、初期化コードがコピーされて移動されます。
これに基づいて、最初の優先順位を導き出すことができます:
クラスの読み込みプロセスは、大きく 3 つの段階に分けることができます: 読み込み -> リンク -> 初期化
初期化段階は 8 つの状況によってトリガーされます周志明》P359 "8クラスの初期化をトリガーするタイプ 状況 ") トリガー:
new キーワードを使用してオブジェクトをインスタンス化する場合
静的フィールドを読み取るか設定しますa type (constant" )
型の静的メソッドの呼び出し
リフレクションを使用してクラスを呼び出す場合
クラスを初期化するときに、親クラスが初期化されていないことが判明した場合、親クラスの初期化が最初にトリガーされます。
親クラスの静的コード>サブクラスの静的コード
3 .static コードは 1 回だけ実行されます。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; }
前の 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 サイトの他の関連記事を参照してください。