Java 泛型深度解析:类型安全、擦除机制与通配符实战
泛型让类型成为参数,实现编译期类型安全、消除强转、代码复用。Java 泛型采用类型擦除实现,运行期泛型信息被擦除为原始类型。
泛型方法
类型参数声明在返回类型前:<E> void printArray(E[] arr)。调用时编译器推断类型。
public static void printArray(E[] arr) {
for (E e : arr) System.out.printf("%s ", e);
System.out.println();
}
Integer[] ints = {1,2,3}; printArray(ints); // 推断 E=Integer
String[] strs = {"A","B"}; printArray(strs); // 推断 E=String
有界类型参数
<T extends Comparable<T>> 限制 T 必须实现 Comparable,保证可比较。
public static > T max(T a, T b, T c) {
T m = a;
if (b.compareTo(m) > 0) m = b;
if (c.compareTo(m) > 0) m = c;
return m;
}
max(3, 4, 5); // 5
max(6.6, 8.8, 7.7); // 8.8
max("pear","apple","orange"); // pear (字典序)
多重界限:<T extends A & B & C>(A 为类,其余为接口,类必须在首位)。
泛型类
public class Box {
private T item;
public void set(T item) { this.item = item; }
public T get() { return item; }
}
Box intBox = new Box<>();
intBox.set(100);
Integer i = intBox.get(); // 无需强转
类型擦除与影响
- 泛型类型编译后变为原始类型(
Box<T>→Box) - 运行期无法区分
List和List - 不能:
new T()、T[] arr = new T[10]、instanceof T、基本类型作类型参数 - 桥接方法:编译器自动生成桥接方法保证多态
擦除导致的问题与对策
// 问题:不能 new T[]
// 对策1:传入 Class 或数组
public T[] toArray(T[] a) { ... }
// 问题:不能 instanceof List
// 对策:检查原始类型后强转(需开发者保证)
if (obj instanceof List) {
List list = (List) obj; // 未检查警告
}
通配符
| 语法 | 含义 | 读/写能力 |
|---|---|---|
? extends T | 上界:T 或其子类 | 只读(获取 T),不可写(除 null) |
? super T | 下界:T 或其父类 | 只写(放入 T),读出仅 Object |
? | 无界:任意类型 | 读出 Object,仅可写 null |
PECS 原则
Producer Extends, Consumer Super
- 只从集合**读取**(生产者) →
? extends T - 只向集合**写入**(消费者) →
? super T - 既读又写 → 无通配符(精确类型)
// 生产者:复制源到目标
public static void copy(List super T> dest, List extends T> src) {
for (T t : src) dest.add(t);
}
泛型最佳实践
- 优先用泛型方法/类,消除非受检警告
- API 设计遵循 PECS,灵活性最大化
- 避免原始类型,除非兼容遗留代码
- 文档化类型参数约束(如
@param)must implement Comparable - 可变参数泛型方法加
@SafeVarargs抑制堆污染警告 - 泛型数组创建用
Array.newInstance或列表转数组
java