Java 7 引入鑽石運算子 <> 簡化泛型實例化,但類型推論規則有邊界情況會導致編譯錯誤。本文分析經典失敗案例,解釋 JLS §18 類型推論規則,提供三種標準解決方案。
public class Holder<T> {
private T value;
public Holder() {}
public Holder(T value) { this.value = value; }
public void setValue(T value) { this.value = value; }
}
// 使用端
Holder<Object> holder = new Holder<>("xxx"); // 編譯錯誤!
incompatible types required: Holder<Object> found: Holder<String>
鑽石運算子的類型參數由 目標類型(賦值左側、傳遞參數類型、回傳類型)推導。但構造器參數 不參與 目標類型推論(JLS §18.5.2)。
流程:
Holder<Object> → 推導 T = ObjectHolder(Object)"xxx" 類型:StringString 是否兼容 Object?是String 更具體,產生衝突實際上 javac 採用「最具體適用類型」策略,構造器參數導致推導為 Holder<String>,與目標類型 Holder<Object> 衝突。
Holder<Object> holder = new Holder<Object>("xxx");
繞過推論,直接鎖定 T=Object,語意最清晰。
Holder<Object> holder = new Holder<>();
holder.setValue("xxx"); // setter 參數類型為 Object,String 可隱式向上轉型
鑽石運算子在無參構造器無推論衝突,setter 呼叫時多態正常運作。
Holder<? extends Object> holder = new Holder<>("xxx");
// 推導為 Holder<String>,賦值給上界通配符合法
// 但之後無法呼叫 setValue(除非傳 null),因 ? extends Object 不接受具體類型
適用於「只讀、不寫入」的 API 邊界。
這是刻意設計:構造器參數不參與目標類型推論,保證「構造器呼叫的類型安全性獨立於賦值語境」。若允許參數反推,會導致同一行代碼在不同賦值目標下行為不一致,破壞局部推理。
| 代碼 | 結果 | 說明 |
|---|---|---|
List<Object> list = new ArrayList<>(); | ✓ OK | 無參構造器,無衝突 |
List<Object> list = new ArrayList<>(Arrays.asList("a")); | ✗ 錯誤 | Collection 建構器參數導致推導為 String |
Map<String, Object> m = new HashMap<>(); m.put("k", "v"); | ✓ OK | put 在構造後呼叫,多態正常 |
Optional<Object> o = Optional.of("x"); | ✗ 錯誤 | of 為靜態工廠方法,參數參與推論 |
var list = new ArrayList<String>(); (Java 10+) 更簡潔<T> T factory() 模式:讓呼叫端語境決定 T,而非實參鑽石運算子是語法糖,非萬能藥。掌握 JLS 類型推論的「目標類型」邊界,遇到衝突時優先選擇顯式類型參數,能避開 90% 的泛型推論陷阱,也能寫出語意更明確的代碼。