Java泛型是JDK 5引入的核心特性,看似简单实则暗藏玄机。本文将从类型擦除机制、通配符应用、PECS原则及实战避坑四个维度进行深度解析。
为什么需要泛型
泛型引入前的问题
java
// Java 5之前的代码
List list = new ArrayList();
list.add("Hello");
list.add(123); // 编译通过,但运行时可能出错
String str = (String) list.get(0); // 需要强制转换
Integer num = (Integer) list.get(1); // 可能抛出ClassCastException
痛点
类型不安全,运行时才暴露问题
需要大量强制类型转换
代码可读性差
泛型引入后的改进
java
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误!
String str = list.get(0); // 无需强制转换
优势:
类型安全检查前置到编译期
消除强制类型转换
代码更清晰、更安全
泛型的底层机制
类型擦除的本质
类型擦除是Java泛型实现的核心机制,在编译期将泛型类型参数替换为边界类型,字节码中不保留泛型信息。
java
// 源代码
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0);
// 编译后(字节码层面)
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // 自动插入强制转换
类型擦除的规则
| 泛型声明 | 擦除后类型 |
|---|---|
List<T> |
List (T无边界→Object) |
List<T extends Number> |
List (T有边界→Number) |
List<T extends Comparable<T>> |
List (T有边界→Comparable) |
类型擦除带来的限制
java
// ❌ 坑1:无法创建泛型数组
List<String>[] array = new List<String>[10]; // 编译错误
// ✅ 解决方案:使用ArrayList或通配符
List<List<String>> list = new ArrayList<>();
// ❌ 坑2:无法instanceof泛型类型
if (obj instanceof List<String>) {} // 编译错误
// ✅ 解决方案:检查原始类型
if (obj instanceof List) {}
// ❌ 坑3:泛型类型不能用于静态成员
class Generic<T> {
static T value; // 编译错误
static List<T> list; // 编译错误
}
// ❌ 坑4:不能new泛型类型实例
class Box<T> {
T create() {
return new T(); // 编译错误
}
}
// ✅ 解决方案:传入Class对象
T create(Class<T> clazz) {
return clazz.newInstance();
}
// ❌ 坑5:List<String>不是List<Object>的子类
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 编译错误!
通配符解决类型擦除灵活性
三种通配符对比
| 通配符类型 | 语法 | 读取 | 写入 | 使用场景 |
|---|---|---|---|---|
| 无界通配符 | <?> |
Object | 不能写(null除外) | 只读操作 |
| 上界通配符 | <? extends T> |
T或子类 | 不能写(null除外) | 生产者(读) |
| 下界通配符 | <? super T> |
Object | T或子类 | 消费者(写) |
无界通配符<?>
java
// 场景:通用集合打印工具
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 可以使用
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());
// 不能添加元素(null除外)
list.add("hello"); // 编译错误
list.add(null); // 可以
上界通配符 <? extends T>
java
// 场景:数值集合求和工具
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
// 可以使用
sum(new ArrayList<Integer>());
sum(new ArrayList<Double>());
sum(new ArrayList<Number>());
// ❌ 不能添加元素(null除外)
numbers.add(1); // 编译错误
为什么不能写? 因为编译器不知道具体是哪个子类:
java
List<? extends Number> list = new ArrayList<Integer>();
list.add(new Double(1.0)); // 如果允许,会破坏类型安全!
下界通配符 <? super T>
java
// 场景:数据批量插入工具
public static void addNumbers(List<? super Integer> list) {
for (int i = 0; i < 10; i++) {
list.add(i); // 可以添加Integer及其子类
}
}
// 可以使用
addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());
// ⚠️ 读取只能得到Object
Object obj = list.get(0);
Integer num = (Integer) list.get(0); // 需要强制转换
PECS原则:通配符使用黄金法则
PECS = Producer Extends, Consumer Super
| 角色 | 通配符 | 记忆方法 |
|---|---|---|
| 生产者(Producer) | <? extends T> |
从集合读取数据,用extends |
| 消费者(Consumer) | <? super T> |
向集合写入数据,用super |
实战示例:Collections.copy
java
// JDK源码中的经典应用
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
// dest是消费者(写入)→ ? super T
// src是生产者(读取)→ ? extends T
}
// 使用示例
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();
Collections.copy(dest, src); // 正确
完整实战:通用数据处理工具类
java
public class GenericUtils {
// 生产者:只读取
public static <T> T getFirst(List<? extends T> list) {
return list.isEmpty() ? null : list.get(0);
}
// 消费者:只写入
public static <T> void addAll(List<? super T> dest, List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}
// 既读又写:不使用通配符
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);
}
}
泛型类、接口与方法
泛型类
java
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();
泛型接口
java
public interface Comparable<T> {
int compareTo(T other);
}
public class Person implements Comparable<Person> {
private String name;
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
}
泛型方法
java
// 泛型方法:类型参数在返回值前声明
public static <T> T getMiddle(T[] array) {
return array[array.length / 2];
}
// 调用时类型可推断
String[] strings = {"a", "b", "c"};
String middle = getMiddle(strings);
高级技巧与最佳实践
泛型方法 vs 泛型类
java
// 优先使用泛型方法(更灵活)
public static <T> List<T> asList(T... items) {
return Arrays.asList(items);
}
// 而不是让调用者指定类类型参数
类型安全的异构容器
java
// 使用Class作为类型令牌
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);
避免的常见误区
| 误区 | 正确做法 |
|---|---|
List<Object> 可以接收任何List |
使用 List<?> 或 List<? extends T> |
| 泛型信息运行时可用 | 使用TypeToken或Class令牌 |
| 可以创建泛型数组 | 使用 List<List<T>> 替代 |
<? extends T> 可以写入 |
只能读取(null除外) |
总结
类型擦除:编译期替换为边界类型,运行时泛型信息不可见
通配符:<?> <? extends T> <? super T>
PECS原则:生产者extends,消费者super
限制:不能new泛型、不能泛型数组、不能instanceof泛型
最佳实践:优先泛型方法、使用Class令牌、避免裸类型