Skip to content

泛型在集合中的应用

集合是泛型的最佳代言

Java 集合框架从 JDK 5 开始全面引入泛型,可以说:没有泛型,就没有现代 Java 集合

因为集合天然需要「装什么类型,由使用者决定」,泛型完美匹配这个需求。

基本使用

List

java
// 没有泛型
List list = new ArrayList();
list.add("hello");
list.add(123); // 鬼知道里面有什么

// 有泛型
List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
String first = names.get(0); // 类型安全,不需要转型

Set

java
Set<String> fruits = new HashSet<>();
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("苹果"); // 重复,自动忽略
// fruits.add(123); // 编译错误

Map

java
Map<String, Integer> ages = new HashMap<>();
ages.put("张三", 25);
ages.put("李四", 30);

int zhangAge = ages.get("张三"); // 返回 Integer,自动拆箱

泛型与继承关系

这是最容易出错的地方:

java
// List<String> 是 List 吗?是的
List<String> strings = new ArrayList<>();
Collection<String> collection = strings; // OK

// List<String> 是 List<Object> 吗?不是!
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 编译错误!

// Why? 假设可以赋值:
objects.add(123); // 往 String 列表里塞整数
String s = strings.get(0); // 运行时 ClassCastException

记住:泛型是不变的,List<String> 不是 List<Object> 的子类型。

通配符打破不变性

如果真的需要「各种类型的集合」,用通配符:

java
// 读取:生产者用 extends
public double sum(List<? extends Number> numbers) {
    double total = 0;
    for (Number n : numbers) { // 只能读
        total += n.doubleValue();
    }
    return total;
}

// 写入:消费者用 super
public void addNumbers(List<? super Integer> list) {
    list.add(1); // 只能写 Integer
    list.add(2);
}

泛型方法

集合操作中泛型方法非常有用:

java
public class Collections {
    // 交换任意类型的两个元素
    public static <T> void swap(List<T> list, int i, int j) {
        T temp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, temp);
    }

    // 二分查找
    public static <T extends Comparable<? super T>> int binarySearch(
            List<? extends T> list, T key) {
        // ...
    }

    // 取最大最小
    public static <T extends Object & Comparable<? super T>> T max(
            Collection<? extends T> coll) {
        // ...
    }
}

泛型与类型推断

JDK 8 增强了类型推断:

java
List<String> list = new ArrayList<>(); // Diamond 语法,编译器推断 String

// 编译器自动推断 T = String
Collections.addAll(list, "a", "b", "c");

// 方法链式调用
List<String> filtered = list.stream()
    .filter(s -> s.length() > 1)
    .collect(Collectors.toList());

泛型与工具类

JDK 9+ 的不可变集合工厂方法:

java
// JDK 9+
List<String> immutable = List.of("a", "b", "c");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);

// JDK 10+
var list = List.of(1, 2, 3); // 类型推断

常见错误

错误一:往泛型集合里加错误类型

java
List<Number> numbers = new ArrayList<>();
numbers.add(1.5); // OK,Double 是 Number 的子类
numbers.add(1);    // OK,Integer 是 Number 的子类

// 但如果是这样:
List<Integer> ints = new ArrayList<>();
List<Number> numbers = ints; // 编译错误!不变性
numbers.add(1.5); // 假设允许,破坏 ints 的类型安全

错误二:在增强 for 循环中修改

java
List<String> list = new ArrayList<>();
list.add("a");

for (String s : list) {
    if (s.equals("a")) {
        list.remove(s); // ConcurrentModificationException!
    }
}

// 正确做法:使用 Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("a")) {
        it.remove();
    }
}

错误三:泛型数组

java
// 不能创建泛型数组
String[] arr = new String[10]; // OK
// List<String>[] listArr = new List<String>[10]; // 编译错误

// 可以绕过但危险
List<String>[] listArr = new ArrayList[10]; // 警告!
listArr[0] = new ArrayList<String>();
listArr[0].add("hello");

PECS 原则回顾

Collections API 完美体现了 PECS:

java
// Collections.copy 用法:dest 消费,src 生产
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) { // 从 src 读(生产者用 extends)
        dest.add(item);  // 往 dest 写(消费者用 super)
    }
}

总结

集合中的泛型要点:

要点说明
声明时指定类型List<String> 告诉编译器只能放 String
类型安全编译期检查,运行时无强制转换
不变性List<String> 不是 List<Object>
PECS生产者用 extends,消费者用 super
Diamond 语法new ArrayList<>() 让编译器推断类型

记住:泛型是 Java 集合的标配,用好泛型,你的代码会更安全、更简洁。

基于 VitePress 构建