Java 泛型(Generics)是 Java SE 5.0 中引入的一項重要特性,它允許在定義類、接口和方法時使用類型參數。泛型的引入旨在提高代碼的類型安全性、可讀性和複用性,同時消除強制類型轉換的麻煩,並在編譯時捕獲更多錯誤。

1. 為什麼需要泛型?

在泛型出現之前,Java 使用 Object 類型來處理不同類型的數據,但這帶來了兩個主要問題:

  1. 類型不安全: 在編譯時無法檢查類型,運行時可能出現 ClassCastException。
  2. 強制類型轉換: 每次從集合中取出元素時都需要進行強制類型轉換,代碼冗餘且易錯。

考慮一個簡單的例子,一個存儲整數的列表:

code Java

downloadcontent_copy

expand_less

import java.util.ArrayList;
import java.util.List;

public class LegacyListExample {
    public static void main(String[] args) {
        List list = new ArrayList(); // 沒有指定類型
        list.add(10);
        list.add("Hello"); // 理論上這裏可以添加任何類型

        Integer num = (Integer) list.get(0); // 需要強制類型轉換
        // String str = (String) list.get(1); // 如果這裏是Integer,就會在運行時出錯
        System.out.println("Number: " + num);

        // 如果不小心取出了錯誤類型,運行時會拋出 ClassCastException
        // Integer wrongNum = (Integer) list.get(1); // 運行時錯誤!
    }
}

這段代碼在編譯時不會報錯,但如果嘗試將 list.get(1) 轉換為 Integer,就會在運行時拋出 ClassCastException。

通俗易懂的理解java泛型_#java

2. 泛型的基本語法

泛型的核心思想是允許類型作為參數傳遞。我們用尖括號 <> 來定義類型參數。

2.1 泛型類

定義一個泛型類,可以在類名後面加上 <T>,其中 T 是類型參數的佔位符,可以代表任何類型。

code Java

downloadcontent_copy

expand_less

// 泛型類示例:一個簡單的盒子,可以存放任何類型的數據
public class Box<T> {
    private T content; // content 的類型由 T 決定

    public Box(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    public static void main(String[] args) {
        // 創建一個存放 Integer 類型的 Box
        Box<Integer> integerBox = new Box<>(123);
        System.out.println("Integer Box Content: " + integerBox.getContent()); // 無需強制轉換

        // 創建一個存放 String 類型的 Box
        Box<String> stringBox = new Box<>("Hello Generics");
        System.out.println("String Box Content: " + stringBox.getContent()); // 無需強制轉換

        // 編譯時類型檢查:
        // integerBox.setContent("World"); // 編譯錯誤:不兼容的類型
    }
}

使用泛型後,Box<Integer> 只能存放 Integer 類型或其子類,Box<String> 只能存放 String 類型或其子類。這大大提高了代碼的類型安全性。

2.2 泛型接口

泛型接口的定義與泛型類類似,在接口名後加上類型參數。

code Java

downloadcontent_copy

expand_less

// 泛型接口示例:一個數據轉換器
public interface Converter<S, T> {
    T convert(S source);
}

// 實現泛型接口:將 String 轉換為 Integer
class StringToIntegerConverter implements Converter<String, Integer> {
    @Override
    public Integer convert(String source) {
        return Integer.parseInt(source);
    }

    public static void main(String[] args) {
        Converter<String, Integer> converter = new StringToIntegerConverter();
        Integer result = converter.convert("456");
        System.out.println("Converted Integer: " + result);

        // Converter<Integer, String> anotherConverter = new StringToIntegerConverter(); // 編譯錯誤
    }
}
2.3 泛型方法

泛型方法可以在普通類或泛型類中定義。它的類型參數是在方法聲明中定義的,獨立於類的類型參數。

code Java

downloadcontent_copy

expand_less

public class GenericMethodExample {

    // 泛型方法:打印數組中的所有元素
    public static <E> void printArray(E[] inputArray) {
        for (E element : inputArray) {
            System.out.printf("%s ", element);
        }
        System.out.println();
    }

    // 泛型方法:獲取兩個對象中較大的一個(需要實現 Comparable 接口)
    public static <T extends Comparable<T>> T maximum(T x, T y) {
        T max = x;
        if (y.compareTo(max) > 0) {
            max = y;
        }
        return max;
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

        System.out.print("Integer Array: ");
        printArray(intArray); // 編譯器會自動推斷 E 為 Integer

        System.out.print("Double Array: ");
        printArray(doubleArray); // 編譯器會自動推斷 E 為 Double

        System.out.print("Character Array: ");
        printArray(charArray); // 編譯器會自動推斷 E 為 Character

        System.out.println("Max of (3, 5): " + maximum(3, 5));
        System.out.println("Max of (apple, banana): " + maximum("apple", "banana"));
    }
}

在 printArray 方法中,類型參數 <E> 在 void 之前定義。在使用時,編譯器會根據傳入的參數自動推斷 E 的具體類型。

3. 類型通配符

類型通配符 (?) 提供了更大的靈活性,它代表未知類型。

3.1 上界通配符 (? extends T)

限制類型只能是 T 或 T 的子類。這表示我們可以從結構中讀取 T 類型的元素,但不能往裏寫入(除了 null)。

code Java

downloadcontent_copy

expand_less

import java.util.ArrayList;
import java.util.List;

public class UpperBoundedWildcard {

    // 接收任何 List,只要其元素是 Number 或 Number 的子類
    public static void printNumbers(List<? extends Number> list) {
        for (Number n : list) {
            System.out.println(n);
        }
        // list.add(new Integer(10)); // 編譯錯誤!不能添加元素(除了null)
    }

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        printNumbers(integers);

        List<Double> doubles = new ArrayList<>();
        doubles.add(3.14);
        doubles.add(2.71);
        printNumbers(doubles);

        // List<String> strings = new ArrayList<>();
        // strings.add("hello");
        // printNumbers(strings); // 編譯錯誤:String 不是 Number 的子類
    }
}

上界通配符主要用於讀取數據,遵循 PECS 原則中的 Producer-Extends(生產者使用 extends)。

3.2 下界通配符 (? super T)

限制類型只能是 T 或 T 的父類。這表示我們可以往結構中寫入 T 類型的元素(或 T 的子類),但從結構中讀取時,只能保證是 Object 類型。

code Java

downloadcontent_copy

expand_less

import java.util.ArrayList;
import java.util.List;

public class LowerBoundedWildcard {

    // 接收任何 List,只要其元素是 Integer 或 Integer 的父類
    public static void addIntegers(List<? super Integer> list) {
        list.add(10); // 可以添加 Integer 及其子類
        list.add(20);
        // Integer i = list.get(0); // 編譯錯誤!只能保證是 Object
        Object obj = list.get(0); // 讀取時只能作為 Object
        System.out.println("Added to list. First element (as Object): " + obj);
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        addIntegers(numbers); // 可以將 Integer 添加到 List<Number>
        System.out.println("Numbers list: " + numbers);

        List<Object> objects = new ArrayList<>();
        addIntegers(objects); // 可以將 Integer 添加到 List<Object>
        System.out.println("Objects list: " + objects);

        // List<Double> doubles = new ArrayList<>();
        // addIntegers(doubles); // 編譯錯誤:Double 不是 Integer 的父類
    }
}

下界通配符主要用於寫入數據,遵循 PECS 原則中的 Consumer-Super(消費者使用 super)。

通俗易懂的理解java泛型_#java_02

3.3 無界通配符 (<?>)

<?> 表示可以匹配任何類型。它常用於不知道集合中具體類型,但又需要操作集合的情況。

code Java

downloadcontent_copy

expand_less

import java.util.ArrayList;
import java.util.List;

public class UnboundedWildcard {

    // 打印任何類型的列表
    public static void printList(List<?> list) {
        for (Object o : list) { // 只能作為 Object 讀取
            System.out.println(o);
        }
        // list.add("test"); // 編譯錯誤!不能添加元素(除了null)
    }

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(10);
        integers.add(20);
        printList(integers);

        List<String> strings = new ArrayList<>();
        strings.add("Apple");
        strings.add("Banana");
        printList(strings);
    }
}

無界通配符也遵循 PECS 原則,因為它既不能安全地添加元素,也無法安全地以具體類型讀取,因此主要用於通用操作,例如 list.size()、list.clear() 等,或者在處理不知道具體類型的集合時。

4. 泛型擦除 (Type Erasure)

Java 泛型是偽泛型,這意味着泛型信息只存在於編譯時期,在運行時會被擦除。在編譯後,所有的泛型類型都會被替換為它們的上界(如果沒有指定上界,則替換為 Object)。

code Java

downloadcontent_copy

expand_less

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class TypeErasureExample {
    public static void main(String[] args) throws Exception {
        List<Integer> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();

        System.out.println(list1.getClass() == list2.getClass()); // 輸出 true

        // 通過反射,可以繞過泛型類型檢查
        list1.add(1);
        Method addMethod = list1.getClass().getMethod("add", Object.class);
        addMethod.invoke(list1, "Hello World"); // 成功添加 String 類型到 List<Integer>

        System.out.println(list1); // 輸出: [1, Hello World]
        // Integer i = list1.get(1); // 運行時會拋出 ClassCastException
    }
}

從上面的例子可以看出,List<Integer> 和 List<String> 在運行時實際上是同一個類型 List。泛型擦除是 Java 為了兼容早期版本而做出的設計選擇,但也帶來了一些限制:

  • 無法在運行時獲取泛型類型信息: instanceof 和 new T() 是不允許的。
  • 泛型數組創建限制: new T[size] 是不允許的。
  • 原始類型與泛型類型的兼容性問題。

5. 泛型中的一些最佳實踐和常見問題

  • PECS 原則:
  • Producer Extends(生產者使用 extends):如果你需要從泛型集合中讀取數據,使用 <? extends T>。
  • Consumer Super(消費者使用 super):如果你需要向泛型集合中寫入數據,使用 <? super T>。
  • 如果既要讀又要寫,那麼就不要使用通配符,直接使用確切的類型 T。

通俗易懂的理解java泛型_#偽泛型_03

  • 泛型與數組: 由於泛型擦除,不能直接創建泛型數組(如 new T[size])。如果確實需要,可以創建 Object 數組然後進行強制類型轉換,或者使用 ArrayList 等泛型集合。
  • 泛型和基本數據類型: 泛型類型參數不能是基本數據類型(如 int, char, boolean),必須是它們的包裝類(Integer, Character, Boolean 等)。這是因為泛型在內部是通過 Object 進行操作的,而基本數據類型不是 Object 的子類。
  • 自定義泛型類型參數命名規範:
  • E - Element (在集合中使用,如 List<E>)
  • K - Key (鍵)
  • V - Value (值)
  • N - Number (數字類型)
  • T - Type (任意類型)
  • S, U, V - 第二、第三、第四個類型
  • 靜態方法中的泛型: 靜態方法不能直接使用類的泛型類型參數。如果靜態方法需要使用泛型,必須自己聲明為泛型方法。 code Java downloadcontent_copy expand_less
public class StaticGenericExample<T> {
    // public static T getSomething(T obj) { // 編譯錯誤!靜態方法不能使用類T
    //    return obj;
    // }

    public static <U> U getSomethingStatic(U obj) { // 正確的泛型靜態方法
        return obj;
    }

    public static void main(String[] args) {
        System.out.println(getSomethingStatic("Hello"));
    }
}

6. 總結

Java 泛型是現代 Java 編程中不可或缺的一部分,它通過在編譯時進行類型檢查,有效地解決了早期 Java 集合的類型安全問題,並減少了運行時錯誤。理解泛型的基本語法、通配符的使用(特別是 PECS 原則)以及泛型擦除的原理,對於編寫健壯、可維護和高效的 Java 代碼至關重要。