泛型在集合中的应用
集合是泛型的最佳代言
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 集合的标配,用好泛型,你的代码会更安全、更简洁。
