java繼承&子父類成員域加載及執行過程

前段時間偶然看到一篇文章,關於java繼承的,講其加載順序,然後我就看蒙了,雖然接觸過不少時間java,用起來因爲編譯器的存在沒什麼語法問題,但真正用notepad++時,發現細節真的很重要,下面看測試時用的例子。

package one;
import java.util.*;

public class Test extends Parent {
    static int c=getInt(1);//2初始化子類靜態成員域 
    int d=getInt(2);//6初始化子類非靜態成員域
    private String name;
    private int age;
    public Test() {
        name="Tom";
        age=20;//此時可以給成員域賦值,這是在成員域已經進行了默認的賦值之後才進行的操作,也就是說不賦值也不會有編譯錯誤。
        System.out.println("7 子類構造函數執行。注意,父類子類成員初始化構造函數執行生成實例後,new操作終於算完成。");
    }
    public static void main(String[] args) {
         System.out.println("3 執行與對象實例化無關的操作。");//3執行與對象實例化無關的操作。
        Test t = new Test();
        // 7父類子類成員初始化構造函數執行生成實例後,new操作終於算完成。
        System.out.println("8 執行剩餘無需生成實例的代碼。此時注意,t是子類實例,該類中沒有num成員,而此時編譯無錯誤,說明子類t同時是父類的實例  " + t.age+"  "+t.num);
        // 8執行剩餘無需生成實例的代碼。此時注意,t是子類實例,該類中沒有num成員,而此時編譯無錯誤,說明子類t同時是父類的實例
        t.c();
        // 9子類對象執行c方法,雖然父類中同樣有c方法,但不會被執行。 

        System.out.print("\n\n\n下來來看另一個實例:在子類中new父類實例,看看執行的步驟:\n");
        Parent p=new Parent();
        p.c();
        //用父類實例來調用c,看執行的到底是子類還是父類中的方法。

            //System.out.println(" "+p.getInt(1));
            //嘗試用p來調用test類中的方法getInt,編譯器會報錯:找不到符號:類型爲Parent的變量P,一個錯誤。也就是說p爲Parent的實例,但不是子類的實例。

        System.out.print("\n\n\n下來來看另一個實例:在子類中new子類實例,然後賦值給父類對象,也就是向上轉型,看看執行的步驟:\n");
        Parent pa=new Test();
        //此時編譯正常通過,向上轉型沒有絲毫問題,不需要強制。
    }

    @Override//注:@Override只是提醒自己這是個與父類同名同參的函數,並無實際作用,切不要被誤導。
    public void c(){
        System.out.println("9 子類對象執行c方法,雖然父類中同樣有c方法,但不會被執行。");
    }

    public static int getInt(int i){
        System.out.println((i==1)?"2 初始化子類靜態成員域":"6 初始化子類非靜態成員域");
        return i;
    }
}
class Parent {
    static int K=1;
    int sum=test(3);
    int num = test(2);//4需要生成子類實例時,先初始化父類非靜態成員域,即便沒有使用到的成員也會被初始化,這可以看出,是按照順序初始化的,
    static int sam=test(K);//1從子類main方法入口進入後,最先初始化父類靜態成員域。“在類被加載的同時會將靜態成員進行加載。而且看我這裏調用的是K的值,如果只是加載了靜態成員而沒有被賦值的話,那K應該爲0,然而此時K爲1”
    public Parent() {
        System.out.println("5 執行父類構造函數,生成父類實例對象。");
        //5執行父類構造函數,生成父類實例對象。
    }
    public static int test(int i) {
        switch(i){
            case 1:{
                System.out.println("1 從子類main方法入口進入後,最先初始化父類靜態成員域sam");
                break;
            }
            case 2:{
                System.out.println("4 先初始化父類非靜態成員域num");
                break;
            }
            case 3:{
                System.out.println("4 先初始化父類非靜態成員域sum");
                break;
            }
        }
        return i;
    }
    public void c(){
        System.out.println("被子類覆蓋了的父類方法,子類對象不能直接訪問 ");
    }
}

我知道大家最不喜歡的就是看代碼,因爲它遠不如文字來的形象直觀,並且不能跳躍,其實我也不喜歡,所以,可以對照着輸出來看源碼:
這裏寫圖片描述
爲了我以後方便查看,我把print的內容都換了,這樣就直觀多了,註釋上有序號,對照着輸出看,如果真是懶得看,那直接看從中得出的結論。(注:源碼可執行,有不明的地方可以將其他部分註釋掉單獨看結果。)

如果子類main方法中不執行new操作,那麼執行結果爲:
這裏寫圖片描述
可以看到只有父類和子類的靜態成員域得到初始化,非靜態成員域沒有得到任何初始化,並且可以試着使創建父類對象,可以得出:
1、要創建子類對象時,會自動先創建父類的實例對象。
2、在類被加載的同時會將靜態成員進行加載並得到初始化且能賦值。
3、在類的實例被創建時,非靜態成員纔會得到初始化,然後調用類的構造函數生成實例。
4、子類實例同時是父類實例,可以調用父類方法,成員也是可以調用的(如例子中子類無num,但可以使用),如果父類子類有相同的方法或屬性,優先調用子類中的(使用super可以調用父類中方法及屬性)。
5、父類的實例不是子類的實例。不能調用子類方法。

後面兩個模塊之所以沒有第一到第三步,是因爲我沒有註釋完全,不影響查看。代碼中注字部分是要點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章