本文最后更新于 2025-10-29,文章内容可能已经过时。

一、泛型概述

Java 泛型是 Java 5 引入的重要特性,它提供了编译时类型安全检查机制,并消除了强制类型转换的需要。泛型的本质是"参数化类型",即在编写代码时将类型作为参数传递,而不是在运行时才确定类型。

二、常见泛型符号含义与用途

符号含义常见使用场景示例
TType(类型)表示任意具体类型,常用于类、接口、方法级别的泛型定义public class Box { ... }
EElement(元素)多用于集合类中,表示集合中的元素类型public interface List { ... }
KKey(键)通常用于 Map 中的键类型public interface Map<K, V> { ... }
VValue(值)通常用于 Map 中的值类型public interface Map<K, V> { ... }
?Unknown(未知)表示不确定的类型,用于通配符List<?> list = new ArrayList();
S, U, V第二、第三、第四个类型参数当一个泛型需要多个类型参数时使用public interface BiFunction<T, U, R> { ... }

重要提示:Java 泛型中并没有强制规定必须使用哪些字母作为类型参数,但为了代码的可读性和一致性,社区中形成了这些约定俗成的命名习惯。

三、泛型的基本使用场景

1. 泛型类

使用场景:当你需要创建一个通用的数据结构,例如列表、栈、队列、字典等,这些结构可以处理各种类型的数据时。

public class Box<T> {
    private T item;
    
    public void setItem(T item) {
        this.item = item;
    }
    
    public T getItem() {
        return item;
    }
}

// 使用示例
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String str = stringBox.getItem(); // 自动类型推断,无需强制转换

Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
Integer num = integerBox.getItem();

2. 泛型接口

使用场景:当你需要设计一组具有通用操作的接口时,例如比较器、转换器、工厂等。

public interface Comparator<T> {
    int compare(T o1, T o2);
}

public class StringComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

// 使用示例
Comparator<String> comparator = new StringComparator();
int result = comparator.compare("apple", "banana");

3. 泛型方法

使用场景:当你需要设计一个通用的方法,它可以处理不同类型的参数时。

public class Util {
    // 泛型方法定义
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }
    
    // 泛型方法使用
    public static <T> T getFirstElement(List<T> list) {
        return list.get(0);
    }
}

// 使用示例
Integer[] intArray = {1, 2, 3};
Util.printArray(intArray);

String[] strArray = {"a", "b", "c"};
Util.printArray(strArray);

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String first = Util.getFirstElement(names);

4. 泛型集合框架

使用场景:Java 集合框架是泛型最广泛应用的场景。

// 不使用泛型(存在类型转换问题)
List list = new ArrayList();
list.add("hello");
list.add(123); // 可以添加任何类型
String s = (String) list.get(0); // 运行时可能抛出 ClassCastException

// 使用泛型(类型安全)
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译时会报错
String s = list.get(0); // 自动类型推断,无需强制转换

5. 有界类型参数

使用场景:限制泛型类型为特定类或其子类。

// 上界:T 必须是 Number 或其子类
public class NaturalNumber<T extends Number> {
    private T n;
    
    public NaturalNumber(T n) {
        this.n = n;
    }
    
    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }
}

// 使用示例
NaturalNumber<Integer> i = new NaturalNumber<>(6);
NaturalNumber<Double> d = new NaturalNumber<>(6.0);
// NaturalNumber<String> s = new NaturalNumber<>("6"); // 编译错误

四、泛型通配符的高级用法

1. 无界通配符 ?

public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 使用示例
List<String> strList = Arrays.asList("a", "b", "c");
printList(strList); // 可以接受任何类型的 List

List<Integer> intList = Arrays.asList(1, 2, 3);
printList(intList); // 也可以接受

2. 上界通配符 <? extends T>

用途:表示某种类型及其子类,适合"只读"操作。

public static double sumOfList(List<? extends Number> list) {
    double sum = 0.0;
    for (Number number : list) {
        sum += number.doubleValue();
    }
    return sum;
}

// 使用示例
List<Integer> intList = Arrays.asList(1, 2, 3);
double sumInt = sumOfList(intList); // 可以接受 Integer 列表

List<Double> doubleList = Arrays.asList(1.0, 2.0, 3.0);
double sumDouble = sumOfList(doubleList); // 也可以接受 Double 列表

3. 下界通配符 <? super T>

用途:表示某种类型及其父类,适合"写入"操作。

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

// 使用示例
List<Integer> intList = new ArrayList<>();
addNumbers(intList); // 可以接受 Integer 列表

List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // 也可以接受 Number 列表

List<Object> objectList = new ArrayList<>();
addNumbers(objectList); // 也可以接受 Object 列表,因为 Object 是 Integer 的父类

五、PECS 原则

生产者-消费者原则 (Producer-Extends, Consumer-Super)

  • 如果一个泛型结构是生产者(提供数据),使用 <? extends T>(上界通配符)
  • 如果一个泛型结构是消费者(消费数据),使用 <? super T>(下界通配符)
// 生产者:提供数据,使用 extends
public static void printList(List<? extends Number> list) {
    for (Number number : list) {
        System.out.println(number);
    }
}

// 消费者:消费数据,使用 super
public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

六、泛型的注意事项

  1. 类型擦除:Java 泛型在编译时会进行类型擦除,运行时无法获取泛型的具体类型信息。

    // 编译错误示例
    public class GenericArray<T> {
        private T[] array = new T[10]; // 编译错误
    }
    
  2. 不能创建泛型数组:不能使用 new T[] 创建泛型数组。

    // 编译错误
    List<T>[] listArray = new List<T>[10];
    
  3. 不能使用 instanceof 检查泛型类型:运行时无法检查泛型类型。

    // 编译错误
    if (list instanceof List<String>) { ... }
    
  4. 静态方法/变量不能使用类的泛型类型参数

    public class MyClass<T> {
        // 编译错误
        public static T staticField;
        
        // 编译错误
        public static void staticMethod(T t) { }
    }
    

七、实际应用案例

1. 泛型在集合框架中的应用

// 使用泛型确保类型安全
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // 编译错误

// 自动类型推断,无需强制转换
String first = names.get(0);

2. 泛型在 Map 中的应用

// 使用 K 和 V 表示键值对
Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 30);
ageMap.put("Bob", 25);

// 获取值,类型安全
Integer aliceAge = ageMap.get("Alice");

3. 泛型在工具类中的应用

public class CollectionUtil {
    // 泛型方法:获取集合中的第一个元素
    public static <T> T getFirstElement(List<T> list) {
        return list.get(0);
    }
    
    // 泛型方法:交换数组元素
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

// 使用示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String first = CollectionUtil.getFirstElement(names);

Integer[] numbers = {1, 2, 3};
CollectionUtil.swap(numbers, 0, 2);

八、总结

  • T:最通用的类型参数,适用于大多数泛型场景
  • E:集合框架中表示元素类型
  • K/V:Map 中表示键和值的类型
  • ?:通配符,表示未知类型,配合上界和下界使用
  • S/U/V:多个泛型参数时的扩展使用

泛型提供了类型安全、代码复用和可读性的优势,是 Java 语言中非常重要的特性。在实际开发中,合理使用泛型可以大大提高代码的质量和可维护性。

重点备注:泛型是编译时的特性,运行时会被擦除。理解类型擦除机制有助于避免常见的泛型使用错误。