Java 泛型 (Generics)
泛型讓類別和方法可以操作不同型別,同時保持型別安全。
為什麼需要泛型?
沒有泛型時:
// 沒有泛型
List list = new ArrayList();
list.add("Hello");
list.add(123); // 可以加入任何型別
String s = (String) list.get(0); // 需要強制轉型
String n = (String) list.get(1); // ClassCastException!
使用泛型:
// 有泛型
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 編譯錯誤
String s = list.get(0); // 不需要轉型
泛型類別
public class Box<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(123);
Integer n = intBox.get();
更多說明請參考 Java 泛型類別。
泛型方法
public class Utils {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static <T> T getFirst(List<T> list) {
if (list.isEmpty()) {
return null;
}
return list.get(0);
}
}
// 使用
String[] strings = {"A", "B", "C"};
Utils.printArray(strings);
Integer[] numbers = {1, 2, 3};
Utils.printArray(numbers);
List<String> list = Arrays.asList("Hello", "World");
String first = Utils.getFirst(list);
更多說明請參考 Java 泛型方法。
多個型別參數
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; }
}
// 使用
Pair<String, Integer> pair = new Pair<>("Age", 25);
String key = pair.getKey(); // "Age"
Integer value = pair.getValue(); // 25
型別邊界
extends(上界)
限制型別必須是某類別或其子類別:
// T 必須是 Number 或其子類別
public class NumberBox<T extends Number> {
private T number;
public double getDoubleValue() {
return number.doubleValue();
}
}
NumberBox<Integer> intBox = new NumberBox<>();
NumberBox<Double> doubleBox = new NumberBox<>();
// NumberBox<String> stringBox; // 編譯錯誤
多個邊界
// T 必須同時實作 Comparable 和 Serializable
public class Data<T extends Comparable<T> & Serializable> {
// ...
}
萬用字元
? extends(上界萬用字元)
// 可以接受 Number 或其子類別的 List
public void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.0, 2.0, 3.0);
printNumbers(intList); // OK
printNumbers(doubleList); // OK
? super(下界萬用字元)
// 可以接受 Integer 或其父類別的 List
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Integer> intList = new ArrayList<>();
List<Number> numList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
addNumbers(intList); // OK
addNumbers(numList); // OK
addNumbers(objList); // OK
PECS 原則
- Producer Extends:讀取資料用
? extends - Consumer Super:寫入資料用
? super
// 從 src 讀取,寫入 dest
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}
更多說明請參考 Java 泛型萬用字元。
型別擦除
泛型只在編譯時期存在,執行時期會被擦除:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
// 執行時期,兩者的 class 相同
System.out.println(list1.getClass() == list2.getClass()); // true
限制
由於型別擦除,泛型有一些限制:
// 不能建立泛型陣列
// T[] array = new T[10]; // 錯誤
// 不能使用 instanceof
// if (obj instanceof T) { } // 錯誤
// 不能建立泛型實例
// T instance = new T(); // 錯誤
實際應用
泛型工具方法
public class CollectionUtils {
public static <T> boolean isEmpty(Collection<T> collection) {
return collection == null || collection.isEmpty();
}
public static <T> T getOrDefault(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
}
泛型介面
public interface Repository<T, ID> {
T findById(ID id);
List<T> findAll();
void save(T entity);
void delete(ID id);
}
public class UserRepository implements Repository<User, Long> {
@Override
public User findById(Long id) { /* ... */ }
// ...
}