泛型編程:讓代碼更安全的類型魔法

你是否遇到過這樣的尷尬:在使用集合時存入了字符串,取出來卻想當作整數使用,結果程序直接崩潰?或者寫了一個排序方法,卻發現只能給整數數組排序,換了字符串數組就得重寫一遍?今天我們要學的泛型編程,就是解決這些問題的"黑科技"!

為什麼需要泛型?從一個崩潰案例説起

先看一段沒有泛型的代碼:

複製

List list = new ArrayList();
list.add("Java");
list.add(21);
String str = (String) list.get(1); // 運行時崩潰!

這段代碼在編譯時完全正常,但運行時會拋出 ClassCastException——我們把整數強行轉換成了字符串!這就是類型轉換不安全的問題。

再看另一個場景:如果要實現一個"交換數組兩個元素"的方法,沒有泛型的話,你可能需要這樣寫:

複製

// 交換整數數組元素
public static void swapInt(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

// 交換字符串數組元素
public static void swapString(String[] arr, int i, int j) {
    String temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

// ... 還得為每個類型寫一個!

簡直是重複勞動的噩夢!而泛型能讓你用一套代碼適配所有類型,同時保證類型安全。

泛型基礎:定義與使用

泛型類:讓類成為"類型模板"

最常見的泛型用法是定義泛型類。比如我們熟悉的 ArrayList 就是一個泛型類,它的定義類似這樣:

複製

public class ArrayList<E> {
    private E[] elements; // 用泛型變量存儲元素

    public boolean add(E e) { // 方法參數使用泛型
        // ... 添加元素邏輯
    }

    public E get(int index) { // 方法返回值使用泛型
        return elements[index];
    }
}

這裏的 <E> 就是類型參數,你可以把它理解為一個"類型佔位符"。使用時只需指定具體類型:

複製

// 創建存儲字符串的列表,只能添加String類型
List<String> strList = new ArrayList<String>();
strList.add("Java");
strList.add(21); // 編譯報錯!類型不匹配

// 創建存儲整數的列表
List<Integer> intList = new ArrayList<>(); // JDK 7+支持菱形語法,右側類型可省略
intList.add(21);
int num = intList.get(0); // 無需強制類型轉換

泛型接口:定義通用行為

泛型接口與泛型類類似,例如Java集合框架中的 List 接口:

複製

public interface List<E> {
    boolean add(E e);
    E get(int index);
    // ...
}

實現泛型接口時有兩種選擇:

  1. 指定具體類型

複製

public class StringList implements List<String> {
    // 所有E都替換為String
    public boolean add(String e) { ... }
    public String get(int index) { ... }
}

  1. 繼續保留泛型

複製

public class MyList<E> implements List<E> {
    // 保持泛型特性
    public boolean add(E e) { ... }
    public E get(int index) { ... }
}

泛型方法:單個方法的類型抽象

有時候不需要整個類都是泛型,只需要某個方法是泛型的。比如前面的"交換數組元素"問題,用泛型方法可以一次性解決:

複製

public class ArrayUtils {
    // 泛型方法:<T> 聲明類型參數,T作為通用類型
    public static <T> void swap(T[] arr, int i, int j) {
        if (i < 0 || i >= arr.length || j < 0 || j >= arr.length) {
            return; // 邊界檢查
        }
        T temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

// 使用時自動推斷類型
String[] strArr = {"Java", "泛型"};
ArrayUtils.swap(strArr, 0, 1); // 交換字符串數組

Integer[] intArr = {1, 2};
ArrayUtils.swap(intArr, 0, 1); // 交換整數數組

類型通配符:靈活處理未知類型

假設我們要寫一個打印集合元素的方法,該怎麼定義參數類型呢?直接用 List<Object> ?不行,因為 List<String> 並不是 List<Object> 的子類!這時候就需要類型通配符 ?

複製

// 打印任意類型的集合
public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 可以接收各種類型的List
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());

通配符的邊界限定

有時候我們需要限制未知類型的範圍,比如只允許數字類型:

複製

// 上限限定:? extends Number 表示"Number的子類或本身"
public static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number num : list) {
        total += num.doubleValue();
    }
    return total;
}

sum(new ArrayList<Integer>()); // 整數列表可以
sum(new ArrayList<Double>()); // 小數列表可以
sum(new ArrayList<String>()); // 字符串列表不行!編譯報錯

還有下限限定 ? super Type,表示"Type的父類或本身",常用於寫入操作:

複製

// 下限限定:只允許添加Integer及其父類(實際中主要是Object)
public static void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(new Object()); // 編譯報錯!Object是Integer的父類但不能添加
}

類型擦除:泛型的"秘密身份"

你知道嗎?Java的泛型其實是"編譯時語法糖",在編譯後會被類型擦除。也就是説,運行時不存在泛型類型!

比如這段代碼:

複製

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(strList.getClass() == intList.getClass()); // 輸出 true!

編譯後,List<String> 和 List<Integer> 都會被擦除為 List<Object>(如果沒有限定邊界)。這就是為什麼泛型不能用於判斷類型、不能創建泛型數組的原因。

Java21天學習計劃第十天:泛型編程基礎_Java

類型擦除示意圖:泛型類 Info<T> 編譯後會被擦除為 Info,T被替換為Object

如果有邊界限定,比如 <T extends Number>,則 T 會被擦除為 Number:

Java21天學習計劃第十天:泛型編程基礎_泛型_02

帶邊界的類型擦除:<T extends Number> 會被擦除為 Number

泛型限定對比表:extends vs super

表格

複製

限定方式

語法

含義

典型用途

無限定

<?>

任意類型

只讀操作,獲取元素

上限限定

<? extends T>

只能是T及其子類

安全讀取,如求和、查找

下限限定

<? super T>

只能是T及其父類

安全寫入,如添加元素

Java21天學習計劃第十天:泛型編程基礎_泛型_03

泛型限定示例:使用 <T extends Comparable<T>> 限定類型必須可比較

集合框架中的泛型:實戰案例

Java集合框架大量使用泛型,比如 HashSet、HashMap 等。以 HashMap 為例:

複製

// 創建鍵為String、值為Integer的映射
Map<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("Java", 95);
scoreMap.put("泛型", 90);

// 遍歷Map,鍵值類型明確
for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + ": " + value);
}

沒有泛型的話,我們需要不斷進行類型轉換,既麻煩又不安全。

自定義泛型排序器

假設我們要對學生列表按成績排序,用泛型可以寫出通用的排序工具:

複製

// 定義可比較的實體類
class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    // 實現比較方法
    @Override
    public int compareTo(Student o) {
        return Integer.compare(this.score, o.score);
    }

    @Override
    public String toString() {
        return name + "(" + score + ")";
    }
}

// 泛型排序工具類
class Sorter<T extends Comparable<T>> {
    public void sort(List<T> list) {
        // 簡單冒泡排序實現
        for (int i = 0; i < list.size() - 1; i++) {
            for (int j = 0; j < list.size() - 1 - i; j++) {
                if (list.get(j).compareTo(list.get(j + 1)) > 0) {
                    T temp = list.get(j);
                    list.set(j, list.get(j + 1));
                    list.set(j + 1, temp);
                }
            }
        }
    }
}

// 使用示例
public static void main(String[] args) {
    List<Student> students = new ArrayList<>();
    students.add(new Student("張三", 85));
    students.add(new Student("李四", 92));
    students.add(new Student("王五", 78));

    Sorter<Student> sorter = new Sorter<>();
    sorter.sort(students);
    System.out.println(students); // 輸出 [王五(78), 張三(85), 李四(92)]
}

這個排序器不僅能給學生排序,只要實現了 Comparable 接口的類型都能用,比如 Integer、String 等!

泛型使用注意事項

  1. 不能用基本類型作為類型參數
    正確:List<Integer>,錯誤:List<int>
  2. 泛型數組創建受限
    正確:List<String>[] lists = new List[10],錯誤:List<String>[] lists = new List<String>[10]
  3. 泛型不能用於靜態變量
    錯誤:public class MyClass<T> { private static T data; }
  4. 泛型不能用於異常類
    錯誤:public class MyException<T> extends Exception { }

總結:泛型讓代碼煥發新生

泛型編程是Java中非常重要的特性,它帶來了三大好處:

  • 類型安全:編譯時檢查類型,避免運行時轉換錯誤
  • 代碼複用:一套代碼適配多種類型,減少重複勞動
  • 可讀性:代碼中直接體現類型信息,更易理解

從今天開始,告別雜亂的類型轉換,用泛型寫出更優雅、更安全的代碼吧!下一章我們將學習集合框架高級應用,看看泛型如何與集合深度結合。

思考題:為什麼 List<?> 不能添加任何元素(除了null)?歡迎在評論區留下你的答案!