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) { /* ... */ }
    // ...
}