Java 類從載入到物件建立,經歷嚴格的生命週期。本文詳細闡述類載入觸發條件、各成員在方法區與堆中的儲存位置、物件實例化的 9 個步驟,以及靜態區塊、建構區塊、建構子的執行順序差異。
只有「主動使用」類才會觸發載入:
new MyClass()MyClass.staticField、MyClass.staticMethod()java MyClass「被動使用」(如存取常數、透過子類引用父類靜態欄位)不會觸發該類初始化。
載入時將 .class 位元碼存入方法區,非靜態成員變數(實例欄位)不會被載入:
| 類成員 | 儲存區域 | 備註 |
|---|---|---|
| 靜態變數 | 方法區(靜態區) | 類變數,所有實例共享 |
| 靜態方法 | 方法區(靜態區) | |
| 靜態區塊 | 方法區(靜態區) | 類初始化時執行一次 |
| 非靜態方法 | 方法區(非靜態區) | 含建構子,所有實例共享程式碼 |
| 建構區塊 | 方法區(靜態區) | 編譯器複製進每個建構子 |
| 非靜態變數 | 堆(實例內部) | 實例建立時分配 |
關鍵點:非靜態方法載入時只存程式碼,不建立實例;呼叫時需先有實例,JVM 透過 this 區分調用者。
Person p = new Person("zhangsan", 20);
main 框架,建立引用變數 p。Person.class 至方法區(靜態成員、方法、區塊)。p。p.setName("lisi");
setName 框架,含 this 與參數 name。this 指向堆中實例。Person.showCountry();
showCountry 框架,含類引用 Person。| 區塊類型 | 用途 | 執行時機 | 執行次數 |
|---|---|---|---|
靜態區塊 static { } | 類初始化 | 類載入時 | 一次 |
建構區塊 { } | 實例通用初始化 | 建構子前,每次 new | 每次建實例 |
| 建構子 | 特定實例初始化 | 建構區塊後 | 每次建實例 |
class Person {
private String name;
private int age = 0;
private static String country = "cn";
Person(String name, int age) {
this.name = name; this.age = age;
}
static { System.out.println("靜態區塊"); }
{ System.out.println(name + "..." + age); } // 建構區塊
public void setName(String name) { this.name = name; }
public void speak() { System.out.println(name + "..." + age); }
public static void showCountry() { System.out.println("country=" + country); }
}
class StaticDemo {
static { System.out.println("StaticDemo 靜態區塊1"); }
public static void main(String[] args) {
Person p = new Person("zhangsan", 100);
p.setName("lisi");
p.speak();
Person.showCountry();
}
static { System.out.println("StaticDemo 靜態區塊2"); }
}
輸出順序:
StaticDemo 靜態區塊1 StaticDemo 靜態區塊2 靜態區塊 null...0 // 建構區塊(實例變數尚未顯式初始化) lisi...100 // speak() country=cn // showCountry()
final 欄位初始化、序列化、反射等進階主題。