一個類及其對象初始化的過程
一、什麼時候需要初始化一個類
首次建立某個對象時 —> Dog dog = new Dog();
首次訪問某個類的靜態方法或者靜態欄位時 —> Dog.staticFields;
java解譯器就會去找類的路徑,定位已經編譯好的Dog.class檔案。
二、獲得類的資源
然後jvm就會載入Dog.class,產生一個class對象。這個時候如果有靜態方法或者變數,靜態初始化動作都會被執行。這個時候要注意啦,靜態初始化在程式運行過程中只會在Class對象首次載入的時候運行一次。這些資源都會放在jvm的方法區。
方法區:
- 又叫靜態區,跟堆一樣,被所有的線程共用。
- 方法區中包含的都是在整個程式中永遠唯一的元素,包含所有的class和static變數。
三、初始化對象—>Dog dog = new Dog()
1.第一次建立Dog對象先執行上面的一二步
2.在堆上為Dog對象分配足夠的儲存空間,所有屬性和方法都被設定成預設值(數字為0,字元為null,布爾為false,而所有引用被設定成null)
3.執行建構函式檢查是否有父類,如果有父類會先調用父類的建構函式,這裡假設Dog沒有父類,執行預設值欄位的賦值即方法的初始化動作。
4.執行建構函式。 有父類情況下的初始化
假設Dog extends Animal
執行第一步,找出Dog.class檔案,接著在載入過程中發現他有一個基類(通過extends關鍵字),於是先執行Animal類的第一二步,載入其靜態變數和方法,載入結束之後再載入子類Dog的靜態變數和方法。
如果Animal類還有父類就以此類推,最終的基類叫做根基類。
ps:因為子類的static初始化可能會依賴於父類的靜態資源,所以要先載入父類的靜態資源。
接著要new Dog對象,先為Dog對象分配儲存空間->到Dog的建構函式->建立預設的屬性。這裡其建構函式裡面的第一行有個隱含的super(),即父類建構函式,所以這時會跳轉到父類Animal的建構函式。
java會幫我們完成建構函式的補充,Dog實際隱式的建構函式如下
Dog() {
//建立預設的屬性和方法
//調用父類的建構函式super()(可顯式寫出)
//對預設屬性和方法分別進行賦值和初始化
}
父類Animal執行建構函式前也是分配儲存空間- >到其建構函式->建立預設的屬性->發現挖槽我已經沒有父類了,這個時候就給它的預設的屬性賦值和方法的初始化。
接著執行建構函式餘下的部分,結束後跳轉到子類Dog的建構函式。
子類Dog對預設屬性和方法分別進行賦值和初始化,接著完成建構函式接下來的部分。
一、為什麼要執行父類Animal的構造方法才繼續子類Dog的屬性及方法賦值。
- -因為子類Dog的非靜態變數和方法的初始化有可能使用到其父類Animal的屬性或方法,所以子類構造預設的屬性和方法之後不應該進行賦值,而要跳轉到父類的構造方法完成父類對象的構造之後,才來對自己的屬性和方法進行初始化。
- -這也是為什麼子類的建構函式顯示調用父類建構函式super()時要強制寫在第一行的原因,程式需要跳轉到父類建構函式完成父類對象的構造後才能執行子類建構函式的餘下部分。
二、為什麼對屬性和方法初始化之後再執行建構函式其他的部分。
- -因為建構函式中的顯式部分有可能使用到對象的屬性和方法。
Tips:其實這種初始化過程都是為了保證後面資源初始化用到的東西前面的已經初始化完畢了。很厲害,膜拜java的父親們。
說了這麼多還是來個例子吧。
這裡注意main函數也是一個靜態資源,執行Dog類的main函數就是調用Dog的靜態資源。
//父類Animalclass Animal { /*8、執行初始化*/ private int i = 9; protected int j; /*7、調用構造方法,建立預設屬性和方法,完成後發現自己沒有父類*/ public Animal() { /*9、執行構造方法剩下的內容,結束後回到子類建構函式中*/ System.out.println("i = " + i + ", j = " + j); j = 39; } /*2、初始化根基類的靜態對象和靜態方法*/ private static int x1 = print("static Animal.x1 initialized"); static int print(String s) { System.out.println(s); return 47; } } //子類Dogpublic class Dog extends Animal { /*10、初始化預設的屬性和方法*/ private int k = print("Dog.k initialized"); /*6、開始建立對象,即分配儲存空間->建立預設的屬性和方法。 * 遇到隱式或者顯式寫出的super()跳轉到父類Animal的建構函式。 * super()要寫在建構函式第一行 */ public Dog() { /*11、初始化結束執行剩下的語句*/ System.out.println("k = " + k); System.out.println("j = " + j); } /*3、初始化子類的靜態對象靜態方法,當然mian函數也是靜態方法*/ private static int x2 = print("static Dog.x2 initialized");/*1、要執行靜態main,首先要載入Dog.class檔案,載入過程中發現有父類Animal, *所以也要載入Animal.class檔案,直至找到根基類,這裡就是Animal*/ public static void main(String[] args) { /*4、前面步驟完成後執行main方法,輸出語句*/ System.out.println("Dog constructor"); /*5、遇到new Dog(),調用Dog對象的建構函式*/ Dog dog = new Dog(); /*12、運行main函數餘下的部分程式*/ System.out.println("Main Left"); } }
自己猜一下結果,輸出為:
static Animal.x1 initialized
static Dog.x2 initialized
Dog constructor
Animal: animal.i = 9, animal.j = 0
Dog.k initialized
Dog:Dog.k = 47
Dog: Animal.j = 39
Main Left
注意啊,好好看不懂就問我,有點複雜,理清就很清晰了,晚安