這還是從一道Java 的面試題說起。不多說直接看這道面試題:
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();
}
}
求這段程序的輸出。
【良言一句:同志們,別小看了這部分代碼,接下來慢慢解析從類加載器開到最后的輸出這么個漫長的過程JVM他是怎么處理的】
1、我們還是先從類加載開始說起。當這個類被編譯通知后,會在相應的目錄下生成兩個.class 文件。一個是 Base.class,另外一個就是Base$Sub.class。這個時候類加載器將這兩個.class 文件加載到內存
2、靜態代碼塊優先執行,因此先執行Sub 類中的代碼,Sub類中沒有靜態代碼塊
【注意-1】先后順序,是先子在--->在父類,【注意-2】字段的值是放在構造器中,按代碼順序執行進行初始化操作
3、一切初始化(字節碼文件該加載的都加載完成)完畢,進入main方法--看到這里的童鞋們,千萬別眨眼,關鍵的地方上演了--左邊Base b,在這里的代碼中,等於就是說了一句廢話,直接可跳躍,從 new Sub()開始,這個時候會調用Sub類的隱式構造函數。這個隱式構造函數是JVM無償拱手讓給你的。還原Sub 類中的構造函數的本質如下:
public Sub(){
super();//始終在構造函數中的第一行,為什么呢?這是因為在一些軟件的升級中,要兼容老版本的一些功能,父類即原先的一些初始化信息也要保證被執行到,再執行當前
baseName = "sub";
}
4、好了,這個時候執行super()這行代碼也就是跑到父類中去執行了,這個我們要到父類中的構造方法中來瞧瞧。public Base(){callName();},同樣,我們需要需要還原這段代碼的本質:
public Base(){
baseName = "base";//4.1 java里面沒有字段的重寫,只有方法名的重寫,因此這個時候先給父類的字段baseName 分配好內存空間再給baseName 字段進行的賦值
callName();//4.2 callName()方法的調用。這里有一個執行細節需要大家注意:當在父類中方法的執行時,執行的原則是:子類有重寫,執行子類,子類沒有重寫,則在執行父類)。這個時候調用的就是子類的callName 方法了,
}
5、子類的callName方法執行,打印輸出的是子類的baseName 字段的值,而這個時候子類的構造函數中字段的賦值還未執行,因此這個時候輸出的null
6、父類的構造函數執行完畢,這個時候又回到子類當中,從super()的下一行繼續執行,這個時候才進行該字段baseName 分配好存儲空間,隨后賦其值。
這個過程的調用時序圖如下所示:
你看,在第四步的時候還未進行初始化,就先打印其字段值,很顯然這個時候輸出的null。
最后附上其輸出結果:
【謝謝觀賞,此篇完畢】