前段時間偶然看到一篇文章,關於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、父類的實例不是子類的實例。不能調用子類方法。
後面兩個模塊之所以沒有第一到第三步,是因爲我沒有註釋完全,不影響查看。代碼中注字部分是要點。