Java 泛型深度解析
泛型是 Java 类型系统的重要组成,理解泛型能写出更安全、更简洁的代码。
一、为什么需要泛型
1.1 没有泛型的问题
// ❌ 类型不安全
List list = new ArrayList();
list.add("Hello");
list.add(123);
String str = (String) list.get(0); // 需要强制转换
Integer num = (Integer) list.get(1); // 可能 ClassCastException
// ✅ 使用泛型
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误
String str = list.get(0); // 无需转换,类型安全
1.2 泛型优势
- 类型安全:编译期检查类型
- 消除强转:无需手动类型转换
- 代码复用:一套代码适用于多种类型
二、泛型基础
2.1 泛型类
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get();
2.2 泛型方法
// 泛型方法
public static <T> T getFirst(List<T> list) {
return list.get(0);
}
// 使用
String first = getFirst(stringList);
Integer firstNum = getFirst(intList);
2.3 泛型接口
public interface Comparable<T> {
int compareTo(T o);
}
public class Person implements Comparable<Person> {
private String name;
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
}
三、类型擦除
3.1 什么是类型擦除
Java 泛型是伪泛型
↓
编译后泛型信息被擦除
↓
运行时无法获取泛型类型
3.2 擦除规则
// 源码
public class Box<T> {
private T value;
public T get() { return value; }
}
// 编译后(擦除)
public class Box {
private Object value;
public Object get() { return value; }
}
// 泛型方法
public static <T extends Comparable<T>> T max(T a, T b)
// 擦除后
public static Comparable max(Comparable a, Comparable b)
3.3 擦除的限制
// ❌ 不能使用泛型创建数组
T[] array = new T[10]; // 编译错误
// ✅ 正确做法
Object[] array = new Object[10];
T[] result = (T[]) Arrays.copyOf(array, size, clazz);
// ❌ 不能实例化泛型
T obj = new T(); // 编译错误
// ✅ 使用 Class 参数
public <T> T create(Class<T> clazz) {
return clazz.newInstance();
}
3.4 泛型不可协变
// ❌ 数组可协变,但不安全
Object[] objects = new String[10];
objects[0] = new Integer(1); // ArrayStoreException
// ❌ 泛型不可协变
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 编译错误
// 如果允许:
objects.add(new Integer(1)); // 破坏类型安全
四、通配符
4.1 无界通配符 (?)
// 接受任何类型的 List
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 使用
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());
// 限制:只能读(Object),不能写
list.add("Hello"); // 编译错误
4.2 上界通配符 (? extends T)
// 接受 Number 或其子类
public double sum(List<? extends Number> list) {
double sum = 0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
// 使用
sum(Arrays.asList(1, 2, 3)); // Integer
sum(Arrays.asList(1.1, 2.2, 3.3)); // Double
// 限制:只能读(T),不能写
list.add(1); // 编译错误
4.3 下界通配符 (? super T)
// 接受 Integer 或其父类
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
// 使用
addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());
// 限制:只能写(T),读出来是 Object
Object obj = list.get(0);
五、PECS 原则
5.1 原则定义
PECS = Producer Extends, Consumer Super
- 生产者(只读)用 extends
- 消费者(只写)用 super
- 既读又写不用通配符
5.2 实战示例
// 生产者(只读)
public void printAll(List<? extends Animal> animals) {
for (Animal animal : animals) {
System.out.println(animal);
}
}
// 消费者(只写)
public void addCats(List<? super Cat> cats) {
cats.add(new Cat("Mimi"));
cats.add(new Cat("Kitty"));
}
// 既读又写
public void process(List<Animal> animals) {
Animal animal = animals.get(0); // 读
animals.add(new Dog()); // 写
}
5.3 记忆技巧
? extends T → 只能读出 T
? super T → 只能写入 T
extends 向上兼容(子类→父类)
super 向下兼容(父类→子类)
六、泛型最佳实践
6.1 优先使用泛型
// ❌ 原始类型
List list = new ArrayList();
// ✅ 泛型
List<String> list = new ArrayList<>();
6.2 使用菱形操作符
// ❌ 冗余
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();
// ✅ 菱形操作符(JDK 7+)
Map<String, List<Integer>> map = new HashMap<>();
6.3 泛型方法优于泛型类
// ❌ 泛型类
class Collections {
public static <T> List<T> emptyList(Class<T> clazz) {
return new ArrayList<>();
}
}
// ✅ 泛型方法
class Collections {
public static <T> List<T> emptyList() {
return new ArrayList<>();
}
}
6.4 类型安全的异构容器
// 使用 Class 作为 key
public class Container {
private Map<Class<?>, Object> data = new HashMap<>();
public <T> void put(Class<T> type, T value) {
data.put(type, value);
}
public <T> T get(Class<T> type) {
return type.cast(data.get(type));
}
}
// 使用
Container container = new Container();
container.put(String.class, "Hello");
container.put(Integer.class, 123);
String str = container.get(String.class);
Integer num = container.get(Integer.class);
七、常见误区
7.1 泛型数组
// ❌ 错误
List<String>[] array = new ArrayList[10];
// ✅ 正确
List<List<String>> list = new ArrayList<>();
7.2 泛型与异常
// ❌ 不能捕获泛型异常
try {
// ...
} catch (T e) { // 编译错误
}
// ✅ 正确
public <T extends Throwable> void handle(Class<T> clazz) throws Throwable {
try {
// ...
} catch (Throwable e) {
if (clazz.isInstance(e)) {
throw clazz.cast(e);
}
}
}
7.3 泛型与 instanceof
// ❌ 不能检查泛型类型
if (obj instanceof List<String>) { // 编译错误
}
// ✅ 正确
if (obj instanceof List) {
List<?> list = (List<?>) obj;
}
八、总结
泛型核心要点:
| 概念 | 说明 | 示例 |
|---|---|---|
| 类型擦除 | 编译后泛型信息丢失 | List<String> → List |
| **? **(无界) | 任何类型 | List<?> |
| ? extends T | T 或其子类(只读) | List<? extends Number> |
| ? super T | T 或其父类(只写) | List<? super Integer> |
| PECS | 生产者 extends,消费者 super | - |
泛型是类型安全的保障,掌握 PECS 原则能正确使用通配符。