建構子雖長得像方法,但本質完全不同。如同鴨嘴獸雖是哺乳動物卻產卵,建構子有獨特規則體系:
| 維度 | 建構子 | 一般方法 |
|---|---|---|
| 目的 | 建立物件並完成初始化 | 執行業務邏輯、運算、副作用 |
| 名稱 | 必須與類別名完全相同 | 慣例小寫開頭、動詞開頭 (calculateTotal) |
| 回傳型別 | 無 (連 void 都不能寫) | 必須宣告 (含 void) |
| 修飾詞 | public/protected/private/default + 可加註解 | 同上 + static/final/abstract/synchronized/native/strictfp |
| 呼叫方式 | 只能配合 new / this() / super() | 物件.方法() / 類別.靜態方法() / 介面.default方法() |
| 繼承性 | 不被繼承,子類別隱含無參建構子呼叫 super() | 非私有方法可被繼承/覆寫 |
| this 語意 | 呼叫本類別其他建構子 (this(...)),必須為第一行 | 參考當前執行物件實例 |
| super 語意 | 呼叫父類別建構子 (super(...)),必須為第一行 | 呼叫父類別被覆寫方法 (super.method()) |
public class Platypus {
private String name;
private int age;
private String habitat;
// 全參建構子:核心初始化邏輯集中於此
public Platypus(String name, int age, String habitat) {
this.name = name;
this.age = age;
this.habitat = habitat;
}
// 委派給全參建構子,提供預設值
public Platypus(String name, int age) {
this(name, age, "澳洲東部溪流"); // 必須第一行
}
public Platypus(String name) {
this(name, 0); // 連鎖委派
}
public Platypus() {
this("未命名鴨嘴獸"); // 終極委派
}
}
核心規則:this(...) 必須為建構子第一條語句,否則編譯錯誤。一個建構子只能呼叫一個 this(...)。
class Mammal {
protected String birthType;
public Mammal(String birthType) { this.birthType = birthType; }
public void getBirthInfo() { System.out.println("出生方式: " + birthType); }
}
class Platypus extends Mammal {
private String name;
// 子類別建構子隱含/顯式呼叫 super(...)
public Platypus(String name) {
super("產卵哺乳"); // 必須第一行,傳遞父類別所需參數
this.name = name;
}
@Override
public void getBirthInfo() {
super.getBirthInfo(); // 這裡 super 呼叫的是父類別「方法」,非建構子
System.out.println("鴨嘴獸名稱: " + name);
}
}
關鍵差異:建構子中 super(...) 呼叫父建構子 (初始化父狀態);方法中 super.method() 呼叫父方法實作 (複用父邏輯)。兩者語法相似但語意截然不同。
public class Order {
private final String id;
private final List items;
private final OrderStatus status;
private Order(String id, List items) { // 私有建構子
this.id = id;
this.items = List.copyOf(items);
this.status = OrderStatus.CREATED;
}
// 語意清晰的靜態工廠
public static Order createNew(List items) {
return new Order(UUID.randomUUID().toString(), items);
}
public static Order restoreFromSnapshot(String id, List items, OrderStatus status) {
Order order = new Order(id, items);
// 反射或特殊邏輯設定 status,或增加 package-private 建構子
return order;
}
}
優點:有名稱、可回傳子型別/快取實例、支援不可變模式、API 演進彈性大。
public class HttpRequest {
private final String url;
private final String method;
private final Map headers;
private final String body;
private final int timeoutMs;
private final boolean followRedirects;
private HttpRequest(Builder b) {
this.url = b.url;
this.method = b.method;
this.headers = Map.copyOf(b.headers);
this.body = b.body;
this.timeoutMs = b.timeoutMs;
this.followRedirects = b.followRedirects;
}
public static class Builder {
// 必填
private final String url;
private String method = "GET";
private Map headers = new HashMap<>();
private String body = "";
private int timeoutMs = 5000;
private boolean followRedirects = true;
public Builder(String url) { this.url = url; }
public Builder method(String m) { this.method = m; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String b) { this.body = b; return this; }
public Builder timeout(int ms) { this.timeoutMs = ms; return this; }
public Builder followRedirects(boolean f) { this.followRedirects = f; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}
// 使用:流暢 API,參數順序不重要,可選參數一目瞭然
HttpRequest req = new HttpRequest.Builder("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.body("{\"key\":\"value\"}")
.timeout(10000)
.build();
public final class Period {
private final LocalDate start;
private final LocalDate end;
public Period(LocalDate start, LocalDate end) {
this.start = Objects.requireNonNull(start, "start 不可為 null");
this.end = Objects.requireNonNull(end, "end 不可為 null");
if (start.isAfter(end)) throw new IllegalArgumentException("start 不得晚於 end");
// LocalDate 本身不可變,無需複製。若為可變型別 (Date/集合) 必須防禦性複製
}
// 僅提供 getter,無 setter
}
| 陷阱 | 錯誤示範 | 正確做法 | 原因 |
|---|---|---|---|
| 建構子中啟動執行緒 | new Thread(this::run).start(); | 提供 start() 方法由呼叫端控制 | 物件未完全初始化即洩漏 this 引用,導致竞態條件 |
| 建構子呼叫可覆寫方法 | public Parent() { init(); } protected void init() {} | 建構子僅初始化欄位,init() 改為 private/final 或靜態工廠中呼叫 | 子類別覆寫方法存取尚未初始化的子類別欄位 → NPE/邏輯錯誤 |
| 循環委派 | this(a); this(b); 互相呼叫 | 設計單向委派鏈,終點為全參建構子 | 編譯器無法檢測運行期無限遞迴 → StackOverflowError |
| 大量建構子參數 | new User(a,b,c,d,e,f,g,h); | Builder 模式 / 參數物件 (DTO) / 靜態工廠 | 參數順序易誤、可讀性差、擴充困難 |
Unsafe.allocateInstance 分配記憶體 → 填充欄位。private void readObject(ObjectInputStream s) 或 readResolve()。serialVersionUID 明確宣告避免版本不相容。// 自動產生:全參建構子、getter、equals/hashCode、toString、with-er (非標準)
public record OrderId(String value) {
public OrderId {
Objects.requireNonNull(value);
if (value.isBlank()) throw new IllegalArgumentException("訂單號不可空白");
}
}
// 等價於不可變類別,建構子驗證在 canonical constructor 中
// 使用:new OrderId("ORD-20240101-001")
Record 針對「資料載體」場景徹底簡化建構子樣板碼,配合 compact constructor 實現驗證邏輯集中化。
建構子是物件生命的「出生證明」。好的建構子設計讓非法狀態無法表達 (Make illegal states unrepresentable) —— 這是領域驅動設計與函數式程式設計共同追求的核心原則。掌握建構子鏈、工廠模式、Builder 模式、Record,你就掌握了 Java 物件創建的主導權。