1. Java 中物件的初始化順序:誰先、誰後?
在 Java 中建立物件,並不只是「配置記憶體並把值寫進去」。這是一個有明確步驟順序的流程。若把初始化過程想像成火箭發射,每個級段都有它嚴格的順序。
當你寫下:
Person vasya = new Person("Vasya", 30);
Java 會依序執行以下步驟:
- 為物件配置記憶體,並將所有欄位設為其預設值。
- 執行欄位的明確初始化(若你在宣告時直接指定了值)。
- 執行所有非靜態(一般)初始化區塊——依照它們在類別中出現的順序。
- 執行你透過 new 呼叫的建構子本體。
接下來逐一說明每個步驟。
2. 欄位初始化
預設值
當物件剛被建立(在你明確指定任何值之前),它的所有欄位都會取得預設值:
| 欄位型別 | 預設值 |
|---|---|
|
|
|
|
|
|
|
'\u0000'(空字元) |
| 參考型別(String、其他物件) | |
明確初始化
如果你在宣告欄位時就指定了值,該值會在預設值之後、但在建構子執行之前被設定。
public class Person {
private String name = "未命名";
private int age = 18;
}
如果你透過無參數建構子建立物件,這些值會保留下來。若使用帶參數的建構子——通常它們會被覆寫。
3. 初始化區塊:用途與運作方式
在 Java 中可以宣告所謂的非靜態(一般)初始化區塊。每次建立物件時都會執行它們,發生在欄位初始化之後,但在建構子之前。
語法:
public class Person {
{
System.out.println("正在執行非靜態初始化區塊!");
}
}
如果類別中有多個這樣的區塊,它們會依照在類別中出現的順序執行。
含多個區塊的範例
public class Person {
private String name = "未命名";
{
System.out.println("區塊 1: name = " + name);
name = "謎";
}
{
System.out.println("區塊 2: name = " + name);
}
public Person() {
System.out.println("建構子: name = " + name);
}
}
若建立物件:
Person p = new Person();
輸出將會是:
區塊 1: name = 未命名
區塊 2: name = 謎
建構子: name = 謎
為什麼需要初始化區塊?
實務上初始化區塊使用得不多。通常能在區塊中做的事,也可以在欄位宣告處或建構子中完成。不過有時(例如有多個建構子,且部分初始化邏輯需要在所有建構子間共用)初始化區塊會很方便。
4. 建構子:初始化的最後一筆
當所有欄位都已取得值(預設或明確設定),且所有初始化區塊都執行完畢後,接著會呼叫建構子——也就是你在 new 之後指定的那一個。
在建構子中,你通常會設定欄位的最終值、接收參數,並執行其他依賴輸入資料的初始化。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
System.out.println("建構子: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
}
5. 初始化完整順序示範
來寫一個類別,清楚展示初始化的順序。
public class Person {
private String name = "未命名";
private int age = 18;
{
System.out.println("初始化區塊: name = " + name + ", age = " + age);
age = 21;
}
public Person() {
System.out.println("無參數建構子: name = " + name + ", age = " + age);
}
public Person(String name, int age) {
System.out.println("帶參數的建構子: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
public void printInfo() {
System.out.println("Person: name = " + name + ", age = " + age);
}
}
接著在主要類別(例如 Main):
public class Main {
public static void main(String[] args) {
System.out.println("建立物件 p1:");
Person p1 = new Person();
p1.printInfo();
System.out.println("\n建立物件 p2:");
Person p2 = new Person("Petya", 30);
p2.printInfo();
}
}
輸出將大致如下:
建立物件 p1:
初始化區塊: name = 未命名, age = 18
無參數建構子: name = 未命名, age = 21
Person: name = 未命名, age = 21
建立物件 p2:
初始化區塊: name = 未命名, age = 18
帶參數的建構子: name = 未命名, age = 21
Person: name = Petya, age = 30
請注意:在帶參數的建構子中,欄位的值仍是舊的(也就是初始化區塊之後的值),而在建構子結束後,欄位才會取得最終值。
6. 初始化順序圖
這裡有一張簡單的流程圖供視覺化理解:
flowchart TD
A[配置記憶體,欄位預設值] --> B[欄位的明確初始化]
B --> C[執行非靜態初始化區塊]
C --> D[執行建構子]
D --> E[物件可供使用]
7. 特性與細節
靜態欄位與靜態區塊
本講僅討論非靜態(一般)欄位與區塊。靜態欄位與靜態區塊在類別載入時只會執行一次,而不是每次建立物件都執行。更多內容會在封裝相關主題中討論。
何時會初始化?
- 欄位——每次建立物件時。
- 初始化區塊——每次建立物件時。
- 靜態欄位與區塊——在類別載入時只會執行一次。
能否在初始化區塊中存取欄位?
可以!但要注意,如果欄位宣告寫在區塊之下,它其實已經存在;然而在明確初始化之前,讀到的會是預設值。
8. 物件初始化的常見錯誤
錯誤 №1:以為欄位會在其宣告之前就被初始化。
在 Java 中,類別內欄位與初始化區塊的宣告順序很重要。如果你在初始化區塊中存取寫在其之下的欄位,該欄位雖已存在,但尚未明確初始化——因此會是預設值。
錯誤 №2:在區塊與建構子中重複初始化。
新手常在初始化區塊與建構子中重複相同邏輯。較好的做法是只用其中一個,或透過 this(...) 由一個建構子呼叫另一個。
錯誤 №3:以為靜態欄位會在每次建立物件時重新初始化。
靜態欄位與區塊的行為不同——它們在類別載入時只初始化一次。
錯誤 №4:使用未初始化的參考型別欄位。
如果你忘了明確初始化參考型別欄位(例如:String name;),那它會是 null,直到你在初始化區塊或建構子中為它賦值為止。
GO TO FULL VERSION