Java 泛型採用類型擦除,編譯期僅作語法檢查,運行期全部擦除為 Object;C# 泛型由 CLR 直接支持,運行期保留具體類型資訊(具體化)。這一根本差異導致 Java 需要通配符(?)來表達「未知類型的父子關係」,而 C# 則通過 where 子句在聲明處約束類型參數。
| 語法 | 含義 | 典型場景 |
|---|---|---|
List<?> | 無界通配符,等價於 List<? extends Object> | 僅讀取元素、不關心具體類型的容器操作 |
List<? extends Number> | 上界通配符:只能是 Number 或其子類 | 讀取數值類型集合(如求和、最大值),不可寫入 |
List<? super Integer> | 下界通配符:只能是 Integer 或其父類 | 寫入 Integer 及其子類對象,讀取只能作為 Object |
// 無界通配符示例:打印任意 List 內容
public void printAll(List<?> list) {
for (Object o : list) System.out.println(o);
}
// 上界通配符:計算 Number 子類列表的雙精度和
public double sum(List<? extends Number> nums) {
double s = 0;
for (Number n : nums) s += n.doubleValue(); // 只能讀取
return s;
}
// 下界通配符:向列表添加 Integer
public void addIntegers(List<? super Integer> list) {
list.add(10); // OK
list.add(new Integer(20)); // OK
// list.add(new Object()); // 編譯錯誤
}
// 類約束:T 必須是引用類型,U 必須是值類型
class MyClass<T, U> where T : class where U : struct { }
// 介面約束:TKey 必須實現 IComparable、IEnumerable;TVal 必須實現 IMyInterface
interface IMyInterface { }
class Dictionary<TKey, TVal>
where TKey : IComparable, IEnumerable
where TVal : IMyInterface
{
public void Add(TKey key, TVal val) { }
}
// 建構函數約束:T 必須有無參建構函數
class Factory<T> where T : new() {
public T Create() => new T();
}
C# 約束在聲明處綁定,編譯器可生成專用 IL 代碼,運行期無裝箱/拆箱開銷,性能優於 Java 擦除模式。
<T extends Number & Serializable> // 注意用 & 而非逗號
class Apple<T> { }
| 維度 | Java | C# |
|---|---|---|
| 運行期類型 | 擦除為 Object/界限類型 | 保留具體類型(具體化) |
| 多態表達 | 使用通配符 ? extends/super | 聲明處約束 where T : ... |
| 基本類型支持 | 不支持(需裝箱) | 原生支持 int、double 等 |
| 性能 | 裝箱/拆箱、類型轉換開銷 | 零開銷,專用代碼路徑 |
理解兩者設計哲學差異,有助於在跨語言架構設計中做出更合適的泛型建模決策。