Javaクラスのインスタンス化プロセス変数の初期化順序と一般的な筆記試験プログラムの読解問題分析


クラスは、任意のstaticメンバーがアクセスされたときにロードされます(コンストラクタもstaticメソッドです).クラスのロードプロセス全体には、ロード、検証、準備、解析、初期化の5段階が含まれます.ここでは,筆記試験問題で関心を持ち,プログラム出力に影響を及ぼす部分についてのみ議論する.
クラスのロード:
準備フェーズでは、static変数はメソッド領域にメモリを割り当てられ、メモリはゼロ値(static変数の初期化との違いに注意)に初期化されます.
初期化フェーズでは、クラスコンストラクタ()メソッドを実行します(インスタンスコンストラクタ()メソッドとは異なります).仮想機会は、子クラスの()メソッドが実行される前に、親クラスの()メソッドが実行されていることを保証します.
()メソッドを実行する場合は、クラス定義におけるstatic変数の付与文とstaticコードセグメントの書き順に順次実行します.
サブクラスがベースクラスの静的メソッドを呼び出す場合、ベースクラスが独自の静的メソッドを呼び出すことに相当するため、サブクラスのstaticは初期化されません.例は次のとおりです.
Child.sMethodBase(); //         

この文の実行結果は次のとおりです.
  initPrint2     s4:null
      sMethodBase     s4:      s4

オブジェクトの作成:
仮想マシンはnewコマンドに遭遇した場合、まずクラスがロードされたかどうかをチェックし、クラスロードチェックに合格した後、仮想マシンはオブジェクトにメモリを割り当て、メモリを割り当てた後、メモリ空間をゼロ値(オブジェクトヘッダを除く)に初期化します.したがって、オブジェクトのインスタンスフィールドは初期化前にゼロ値になります.
newコマンドを実行すると、インスタンスコンストラクタメソッドが実行され、オブジェクトの初期化が開始されます.
コンストラクタに入ると、ベースクラスがある場合は、ベースクラスの非パラメトリックコンストラクタ(またはsuper()で明示的に指定されたベースクラスコンストラクタ)に入ります.構築する前に、インスタンスフィールドと非staticコードセグメントの書き込み順に順次初期化し、最後にコンストラクタの文を実行します.
super()文はベースクラスの順序でコンストラクタの一番前に置く必要があります.そうしないと、コンパイラはエラーを報告します.
サブクラスオブジェクトを作成する例は次のとおりです.
Child child = new Child("s");

出力結果:
  initPrint2     s4:null
  initPrint2     s2:null
  initPrint1     s3:null
  initPrint1     s4:      s4
      int i
  initPrint1     s1:null
  initPrint1     s2:      s2
     

クラス(static変数の初期化フェーズで1行目、2行目)をロードしてからオブジェクト(3行目以降)を作成することがわかります.作成されたプロシージャも親から子へ、static変数以外の初期化(3行目と6行目に示すように初期化前にデフォルト値が設定されていた)してからコンストラクタ文を実行します.
上で使用したクラスの定義は次のとおりです.
class Base {
    private int x3 = initPrint1();
    public String s3 = "      s3";

    private static int x4 = initPrint2();
    private static String s4 = "      s4";

    private int initPrint1() {
        System.out.println("  initPrint1     s3:" + s3);
        System.out.println("  initPrint1     s4:" + s4);
        return 11;
    }

    private static int initPrint2() {
        System.out.println("  initPrint2     s4:" + s4);
        return 21;
    }

    public Base(int i) {
        System.out.println("      int i");
    }

    public void callName() {
        System.out.println(s3);
    }

    public static void sMethodBase() {
        System.out.println("      sMethodBase     s4:"+s4);
    }
}
class Child extends Base {
    private int x1 = initPrint1();
    public String s1 = "      s1";

    private static int x2 = initPrint2();
    private static String s2 = "      s2";

    private int initPrint1() {
        System.out.println("  initPrint1     s1:" + s1);
        System.out.println("  initPrint1     s2:" + s2);
        return 11;
    }

    private static int initPrint2() {
        System.out.println("  initPrint2     s2:" + s2);
        return 21;
    }

    public Child(String s) {
        super(1);
        System.out.println("     ");
    }

    public void callName() {
        System.out.println(s1);
    }

    public static void sMethodChild() {
        System.out.println("      sMethodChild     s2:"+s2);
    }
}

メソッドとフィールドの書き換え
もう1つの基礎的な問題は、子クラス対親クラスのoverrideです.
メソッドの書き換えは実行時にバインドされる効果があり、サブクラスインスタンスがベースクラスのメソッドを書き換えると、ベースクラスにアップコンバートしても、呼び出されるのはサブクラスのメソッドです.また、メソッドのフィールドもサブクラスのフィールドとして優先されます.
しかし、フィールドは実行時にバインドされるという説はなく、アップグレード後に呼び出されるのがベースクラスのフィールドです.
同時に、静的メソッドはクラスに関連付けられ、単一のオブジェクトに関連付けられているわけではなく、実行時にバインドされていません.
class Base {
    public String s1 = "      s1";
    private static String s2 = "      s2";

    public void f() {
        System.out.println("    ");
    }

}

class Child extends Base {
    public String s1 = "      s1";
    private static String s2 = "      s2";

    public void f() {
        System.out.println("    ");
    }
}

上の2つのクラスについて、次のように使用します.
        Child child = new Child();
        System.out.println(((Base)child).s1);
        ((Base)child).f();

出力された結果は次のとおりです.
      s1
    

なお、privateのメソッドは書き換えることができるが、従来のoverrideではなく、親のprivateメソッドは子クラスに見えないため、子クラス書き換えの関数は新しい関数と考えられ、親関数で子クラスをアップグレードする際に呼び出されるのは親のprivateメソッドであり、これはクラスロードの解析段階で決定される.
class Base {
    public String s1 = "      s1";

    private void f() {
        System.out.println("    ");
    }

    public static void main(String[] args) {
        Child child = new Child();
        System.out.println(((Base)child).s1);
        ((Base)child).f();

    }

}

class Child extends Base {
    public String s1 = "      s1";

    public void f() {
        System.out.println("    ");
    }
}

Baseのmain関数の実行結果は次のとおりです.
      s1
    

解析フェーズで一意の呼び出しバージョンを決定する方法にはstaticメソッド、privateメソッド、インスタンスコンストラクタ、親メソッドの4種類があり、「コンパイラは知っているが、実行期間は変わらない」という要件を満たしています.
総合問題.
最後に牛客のネット上のテーマを見てみましょう.
public class Base
{
    private String baseName = "base";
    public Base()
    {
        callName();
    }
 
    public void callName()
    {
        System. out. println(baseName);
    }
 
    static class Sub extends Base
    {
        private String baseName = "sub";
        public void callName()
        {
            System. out. println (baseName) ;
        }
    }
    public static void main(String[] args)
    {
        Base b = new Sub();
    }
}

プログラムの出力結果は何ですか?
5
4
3
2
1
null(サブクラスは親クラスのpublicメソッドを書き換え、publicインスタンスメソッドは実行時バインドメソッドに属し、実際に呼び出された場合、受信したthisはサブクラスオブジェクトを参照するので、サブクラスの関数にナビゲートしますが、親クラスが構築されていない場合、サブクラスインスタンス変数はまだ初期化されておらず、ゼロ値です).ベースのpublic callName()をprivate callName()に変更した場合、結果はどうなりますか?
5
4
3
2
1
ベース(サブクラスには親を本当に書き換えるcallName()メソッドはありません.これらは2つの異なるメソッドです.privateメソッドはクラスロードフェーズで解析され、「コンパイラは知っていますが、実行期間は変わりません」という要件を満たします.呼び出し元の実際のタイプとは関係なく、コンテキスト情報に基づいて親コンストラクタのメソッドが親クラスのプライベートメソッドとして指定されます).
下一篇:Javaでの反射
上一篇:ThoughWorksプログラミング体験