Java 物件初始化的過程介紹

大雄45發表於2021-05-11
導讀 在Java中,一個物件在可以被使用之前必須要被正確地初始化,這一點是Java規範規定的。在例項化一個物件時,JVM首先會檢查相關型別是否已經載入並初始化,如果沒有,則JVM立即進行載入並呼叫類構造器完成類的初始化。

Java 物件初始化的過程介紹Java 物件初始化的過程介紹

一個類及其物件初始化的過程
什麼時候需要初始化一個類

首次建立某個物件時:

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

1.執行第一步,找出 Dog.class 檔案,接著在載入過程中發現他有一個基類(透過 extends 關鍵字),於是先執行 Animal 類的第一二步,載入其靜態變數和方法,載入結束之後再載入子類 Dog 的靜態變數和方法。
如果 Animal 類還有父類就以此類推,最終的基類叫做根基類。
注意:因為子類的 static 初始化可能會依賴於父類的靜態資源,所以要先載入父類的靜態資源。

2.接著要 new Dog 物件,先為 Dog 物件分配儲存空間 -> 到 Dog 的建構函式 -> 建立預設的屬性。這裡其建構函式里面的第一行有個隱含的 super(),即父類建構函式,所以這時會跳轉到父類 Animal 的建構函式。

Java 會幫我們完成建構函式的補充,Dog 實際隱式的建構函式如下:

Dog() { 
    //建立預設的屬性和方法 
    //呼叫父類的建構函式super()(可顯式寫出) 
    //對預設屬性和方法分別進行賦值和初始化 
}

3、父類 Animal 執行建構函式前也是分配儲存空間 -> 到其建構函式 -> 建立預設的屬性 -> 發現挖槽我已經沒有父類了,這個時候就給它的預設的屬性賦值和方法的初始化。

4、接著執行建構函式餘下的部分,結束後跳轉到子類 Dog 的建構函式。

5、子類 Dog 對預設屬性和方法分別進行賦值和初始化,接著完成建構函式接下來的部分。

一、為什麼要執行父類 Animal 的構造方法才繼續子類 Dog 的屬性及方法賦值?

因為子類 Dog 的非靜態變數和方法的初始化有可能使用到其父類 Animal 的屬性或方法,所以子類構造預設的屬性和方法之後不應該進行賦值,而要跳轉到父類的構造方法完成父類物件的構造之後,才來對自己的屬性和方法進行初始化。

這也是為什麼子類的建構函式顯示呼叫父類建構函式 super() 時要強制寫在第一行的原因,程式需要跳轉到父類建構函式完成父類物件的構造後才能執行子類建構函式的餘下部分。

二、為什麼對屬性和方法初始化之後再執行建構函式其他的部分?

因為建構函式中的顯式部分有可能使用到物件的屬性和方法。

Tips:其實這種初始化過程都是為了保證後面資源初始化用到的東西前面的已經初始化完畢了。很厲害,膜拜 Java 的父親們。

說了這麼多還是來個例子吧。

這裡注意 main 函式也是一個靜態資源,執行 Dog 類的 main 函式就是呼叫 Dog 的靜態資源。

例項

//父類Animal
class 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;  
    }  
}  
 
//子類 Dog
public 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
i = 9, j = 0
Dog.k initialized
k = 47
j = 39
Main Left


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2771720/,如需轉載,請註明出處,否則將追究法律責任。

相關文章