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);  // 編譯錯誤(立即發現)

型別參數命名慣例

符號代表意義
TType(型別)
EElement(元素)
KKey(鍵)
VValue(值)
NNumber(數字)
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 限制型別上界
  • 泛型類別可以繼承和實作介面
  • 型別擦除限制了某些操作