Java 泛型類別
泛型類別讓你可以建立可重複使用的類別,同時保持型別安全。
基本語法
使用角括號 <T> 宣告型別參數:
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 s = stringBox.get(); // 不需要轉型
Box<Integer> intBox = new Box<>();
intBox.set(123);
Integer n = intBox.get();
為什麼需要泛型
沒有泛型的問題
// 沒有泛型:使用 Object
public class OldBox {
private Object content;
public void set(Object content) {
this.content = content;
}
public Object get() {
return content;
}
}
// 問題1:需要轉型
OldBox box = new OldBox();
box.set("Hello");
String s = (String) box.get(); // 需要轉型
// 問題2:執行時錯誤
box.set(123);
String s2 = (String) box.get(); // ClassCastException(執行時才發現)
使用泛型的優點
// 使用泛型:編譯時檢查
Box<String> box = new Box<>();
box.set("Hello");
String s = box.get(); // 不需要轉型
box.set(123); // 編譯錯誤(立即發現)
型別參數命名慣例
| 符號 | 代表意義 |
|---|---|
T | Type(型別) |
E | Element(元素) |
K | Key(鍵) |
V | Value(值) |
N | Number(數字) |
S, U, V | 第二、三、四個型別 |
多個型別參數
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
@Override
public String toString() {
return "(" + key + ", " + value + ")";
}
}
// 使用
Pair<String, Integer> pair = new Pair<>("age", 25);
String key = pair.getKey(); // "age"
Integer value = pair.getValue(); // 25
Pair<Integer, String> reversed = new Pair<>(1, "one");
有界型別參數
上界 (extends)
限制型別必須是某個類別或其子類別:
// T 必須是 Number 或其子類別
public class NumberBox<T extends Number> {
private T number;
public NumberBox(T number) {
this.number = number;
}
public double doubleValue() {
return number.doubleValue(); // 可以使用 Number 的方法
}
}
// 使用
NumberBox<Integer> intBox = new NumberBox<>(10);
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
// NumberBox<String> strBox = new NumberBox<>("Hi"); // 編譯錯誤
多重上界
// T 必須繼承 Number 且實作 Comparable
public class SortableNumber<T extends Number & Comparable<T>> {
private T value;
public SortableNumber(T value) {
this.value = value;
}
public boolean isGreaterThan(T other) {
return value.compareTo(other) > 0;
}
}
注意:類別必須放在介面前面
泛型與繼承
泛型類別可以繼承
public class Box<T> {
protected T content;
}
// 保留泛型
public class ColorBox<T> extends Box<T> {
private String color;
}
// 指定具體型別
public class StringBox extends Box<String> {
// content 的型別固定為 String
}
// 加入新的型別參數
public class PairBox<T, U> extends Box<T> {
private U extra;
}
泛型不是協變的
Box<Integer> intBox = new Box<>();
Box<Number> numBox = intBox; // 編譯錯誤!
// 為什麼?因為這樣會不安全:
// numBox.set(3.14); // 這會把 Double 放入 Integer 的 Box
泛型與介面
public interface Container<T> {
void add(T item);
T get(int index);
int size();
}
public class ListContainer<T> implements Container<T> {
private List<T> items = new ArrayList<>();
@Override
public void add(T item) {
items.add(item);
}
@Override
public T get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
}
實際範例
快取類別
public class Cache<K, V> {
private Map<K, V> cache = new HashMap<>();
private int maxSize;
public Cache(int maxSize) {
this.maxSize = maxSize;
}
public void put(K key, V value) {
if (cache.size() >= maxSize) {
// 簡單的移除策略:移除第一個
K firstKey = cache.keySet().iterator().next();
cache.remove(firstKey);
}
cache.put(key, value);
}
public V get(K key) {
return cache.get(key);
}
public boolean contains(K key) {
return cache.containsKey(key);
}
}
// 使用
Cache<String, User> userCache = new Cache<>(100);
userCache.put("user1", new User("Alice"));
User user = userCache.get("user1");
結果包裝類別
public class Result<T> {
private final T value;
private final String error;
private final boolean success;
private Result(T value, String error, boolean success) {
this.value = value;
this.error = error;
this.success = success;
}
public static <T> Result<T> success(T value) {
return new Result<>(value, null, true);
}
public static <T> Result<T> failure(String error) {
return new Result<>(null, error, false);
}
public boolean isSuccess() { return success; }
public T getValue() { return value; }
public String getError() { return error; }
}
// 使用
public Result<User> findUser(String id) {
User user = repository.findById(id);
if (user != null) {
return Result.success(user);
} else {
return Result.failure("使用者不存在");
}
}
Result<User> result = findUser("123");
if (result.isSuccess()) {
User user = result.getValue();
} else {
System.out.println(result.getError());
}
堆疊實作
public class Stack<E> {
private List<E> elements = new ArrayList<>();
public void push(E item) {
elements.add(item);
}
public E pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
public E peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.get(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
public int size() {
return elements.size();
}
}
型別擦除
Java 的泛型使用型別擦除實作,編譯後型別資訊會被移除:
// 編譯前
Box<String> box = new Box<>();
box.set("Hello");
String s = box.get();
// 編譯後(概念上)
Box box = new Box();
box.set("Hello");
String s = (String) box.get();
限制
public class Box<T> {
// ✗ 不能建立 T 的實例
// T item = new T();
// ✗ 不能建立 T 的陣列
// T[] array = new T[10];
// ✗ 不能用 instanceof 檢查泛型型別
// if (obj instanceof T) { }
// ✗ 不能有靜態的泛型成員
// private static T instance;
}
重點整理
- 泛型類別使用
<T>宣告型別參數 - 提供編譯時型別檢查,避免轉型
- 可以有多個型別參數:
<K, V> - 使用
extends限制型別上界 - 泛型類別可以繼承和實作介面
- 型別擦除限制了某些操作