簡介
Java
泛型是 JDK 5
引入的一項特性,它提供了編譯時類型安全檢測機制,允許在編譯時檢測出非法的類型。泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。
泛型的好處:
-
編譯期檢查類型安全
-
避免強制類型轉換(
cast
) -
代碼更通用,更易重用
泛型的基本使用
泛型類
public class Box<T> {private T content;public void set(T content) {this.content = content;}public T get() {return content;}
}
使用:
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get(); // 無需強轉
泛型方法
public class Util {public static <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);}}
}
使用:
String[] arr = {"A", "B", "C"};
Util.printArray(arr);
泛型接口
public interface Converter<F, T> {T convert(F from);
}public class StringToIntegerConverter implements Converter<String, Integer> {public Integer convert(String from) {return Integer.parseInt(from);}
}
泛型中的通配符:?
? 通配符
表示任意類型:
public void printList(List<?> list) {for (Object item : list) {System.out.println(item);}
}
? extends T(上界通配符)
表示 T
或 T
的子類(只讀,不能寫):
public void printNumbers(List<? extends Number> list) {for (Number num : list) {System.out.println(num);}
}
不能添加元素:
list.add(10); // ? 報錯
? super T(下界通配符)
表示 T
或 T
的父類(可以寫入,但取出只能當作 Object
):
public void addNumbers(List<? super Integer> list) {list.add(10); // ? OK
}
類型擦除(Type Erasure)
泛型在編譯后會被擦除,JVM
不知道泛型類型,全部變成 Object
:
List<String> list = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list.getClass() == list2.getClass()); // true
正因如此:
-
不能使用
new T()
-
不能使用
T.class
-
不能判斷
instanceof T
常見的泛型類庫例子
-
List<T>
:存儲 T 類型的集合 -
Map<K, V>
:泛型鍵值對 -
Optional<T>
:包裝返回值 -
Comparable<T>
:排序比較接口 -
Function<T, R>
:函數式接口 -
Callable<T>
:異步任務的返回類型 -
Future<T>
:異步計算的結果
進階技巧
泛型數組不允許:
List<String>[] lists = new List<String>[10]; // ? 編譯錯誤
泛型靜態方法必須聲明 <T>
:
public static <T> void print(T value) {System.out.println(value);
}
實際項目中 Java 泛型的用法大全
通用返回封裝類(統一 API 響應格式)
public class Result<T> {private int code;private String message;private T data;public Result(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}public static <T> Result<T> success(T data) {return new Result<>(200, "Success", data);}public static <T> Result<T> fail(String message) {return new Result<>(500, message, null);}// getter/setter omitted
}
通用分頁結果類
public class PageResult<T> {private List<T> records;private long total;public PageResult(List<T> records, long total) {this.records = records;this.total = total;}
}
結合 Spring 使用泛型的示例
通用 Service 接口
public interface BaseService<T, ID> {T findById(ID id);List<T> findAll();T save(T entity);void deleteById(ID id);
}
抽象 Service 實現類
public abstract class AbstractBaseService<T, ID> implements BaseService<T, ID> {@Autowiredprotected JpaRepository<T, ID> repository;@Overridepublic T findById(ID id) {return repository.findById(id).orElse(null);}@Overridepublic List<T> findAll() {return repository.findAll();}@Overridepublic T save(T entity) {return repository.save(entity);}@Overridepublic void deleteById(ID id) {repository.deleteById(id);}
}
具體 Service
@Service
public class UserService extends AbstractBaseService<User, Long> {// 可以擴展額外業務邏輯
}
Java 泛型 與 C#.net 泛型比較
Java
泛型 與 C# (.NET)
泛型有很多相似之處,C#
泛型的設計部分參考了 Java
。但它們在類型擦除、協變/逆變、約束、運行時行為等方面有顯著的不同。
特性 | Java 泛型 | C# (.NET) 泛型 |
---|---|---|
類型擦除 | 是(編譯期擦除) | 否(保留類型信息) |
運行時可反射獲取泛型類型 | 否 | 是 |
基本類型支持 | 不直接支持(需使用包裝類如 Integer ) | 支持,例如 List<int> |
泛型約束 | 限制(只能 extends ,不支持多個約束) | 強大(支持 where T : class, new() , 多個接口) |
協變/逆變支持 | 通過通配符 ? extends/super | 直接使用 out/in 關鍵字 |
泛型數組 | 不支持(如 new T[] 編譯錯誤) | 支持 |
泛型方法 | 支持 | 支持 |
類型擦除 vs 保留類型
- Java
List<String> list = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(list.getClass() == intList.getClass()); // true
Java
在編譯時擦除了泛型信息,List<String>
和 List<Integer>
其實是同一個字節碼類。
- C#
List<string> list = new List<string>();
List<int> intList = new List<int>();
Console.WriteLine(list.GetType() == intList.GetType()); // false
C#
會為不同泛型參數生成不同的類實例,因此保留類型信息。
泛型約束
- Java
public class Repository<T extends BaseEntity> {public void save(T entity) { }
}
- C#
public class Repository<T> where T : BaseEntity, new() {public void Save(T entity) { }
}
C#
支持更豐富的泛型約束,例如要求是引用類型 class
、值類型 struct
、必須有無參構造函數 new()
等。
協變與逆變(Covariance & Contravariance)
- Java 使用通配符
List<? extends Number> numbers; // 只能讀取,不能添加
List<? super Integer> integers; // 只能添加 Integer
- C# 使用 in / out
interface ICovariant<out T> { }
interface IContravariant<in T> { }
C#
在接口中用 in/out
明確支持協變逆變,且更強大、更類型安全。
泛型數組
- Java
T[] array = new T[10]; // 編譯錯誤
- C#
T[] array = new T[10]; // 合法
基本類型泛型
- Java
List<int> list = new ArrayList<>(); // 編譯錯誤
List<Integer> list = new ArrayList<>(); // 正確
- C#
List<int> list = new List<int>(); // 正確,泛型支持值類型
總結
特性 | Java | C# |
---|---|---|
類型安全 | ?? | ?? |
靈活性 | ?(類型擦除限制) | ??(運行時保留泛型) |
泛型數組 | ? | ?? |
基本類型支持 | ?(需包裝) | ?? |
泛型約束 | 一般 | 強大 |
協變逆變 | 復雜、通配符語法 | 簡潔、原生支持 |
性能 | 需裝箱 | 無裝箱(對值類型更快) |
協變逆變詳解
協變(Covariance)和逆變(Contravariance)是泛型類型系統中用于處理子類和父類之間的泛型關系的重要機制。
Java 的協變(Covariant)與逆變(Contravariant)
Java
使用 通配符(wildcards
) 來支持:
協變(
? extends T
) — 只讀
-
意思是:某個未知類型是
T
的子類 -
常用于“只能讀”的場景
public void readAnimals(List<? extends Animal> animals) {for (Animal animal : animals) {System.out.println(animal);}
}
可以傳入 List<Cat>、List<Dog> 或 List<Animal>
,但不能添加新元素。
animals.add(new Cat()); // ? 編譯錯誤
逆變(? super T) — 只寫
-
意思是:某個未知類型是
T
的父類 -
常用于“只能寫”的場景
public void addCats(List<? super Cat> cats) {cats.add(new Cat()); // ? 合法// cats.add(new Animal()); // ? 不安全
}
可以傳入 List<Cat>、List<Animal>、List<Object>
,但不能安全地讀取具體類型:
Object obj = cats.get(0); // 只能作為 Object 使用
C# 中的協變與逆變
- C# 協變(out)
interface IReadOnlyList<out T> {T Get(int index);
}IReadOnlyList<Animal> animals = new List<Cat>(); // ? 協變成功
- C# 逆變(in)
interface IWriter<in T> {void Write(T value);
}IWriter<Cat> writer = new AnimalWriter(); // ? 逆變成功
完整示例:協變 & 逆變
import java.util.*;class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }public class VarianceDemo {public static void main(String[] args) {List<Cat> cats = new ArrayList<>();cats.add(new Cat());readAnimals(cats); // ? 協變addCats(cats); // ? 逆變}// 協變:讀取public static void readAnimals(List<? extends Animal> animals) {for (Animal a : animals) {System.out.println("Animal: " + a);}// animals.add(new Dog()); // ? 編譯錯誤}// 逆變:寫入public static void addCats(List<? super Cat> animals) {animals.add(new Cat()); // ?}
}
總結
特性 | 協變 | 逆變 |
---|---|---|
Java 關鍵字 | ? extends T | ? super T |
C# 關鍵字 | out T | in T |
適合場景 | 讀取數據 | 寫入數據 |
是否可寫 | ? | ? |
是否可讀 | ? | ?(只能當 Object) |