读取用 ? extends t,写入用 ? super t;遵循pecs原则:生产者(读取)用extends,消费者(写入)用super。

泛型通配符 ? 什么时候该用 ? extends T,什么时候用 ? super T
核心就一条:读取用 ? extends T,写入用 ? super T。这是由 Java 的“PECS”(Producer Extends, Consumer Super)原则决定的。
比如你有一个 List extends Number>,它可能是 List<integer></integer> 或 List<double></double>,但你不能往里 add 任何具体数字——因为编译器无法保证类型安全;你只能调用 get(),拿到的最多是 Number 类型。
反过来,List super Integer> 可能是 List<integer></integer>、List<number></number>,甚至 List<object></object>。你可以安全地 add(new Integer(42)),但 get() 返回的只能是 Object。
-
? extends T:适合只读场景,如max(List extends Comparable>) -
? super T:适合只写或消费场景,如Collections.copy(List super T>, List extends T>) - 别用裸
?做参数类型,除非你真只需要getClass()或判空这类操作
泛型边界(extends 和 super)对方法重载的影响
泛型边界不参与方法签名擦除后的字节码生成,所以 void f(List<string>)</string> 和 void f(List extends CharSequence>) 编译后都是 void f(List),会导致编译错误:“method f is already defined”。Java 不允许仅靠泛型边界区分重载方法。
立即学习“Java免费学习笔记(深入)”;
常见踩坑点:
- 写工具类时,别试图靠
<t extends number></t>和<t extends comparable></t>写两个同名方法来“重载”逻辑 - 如果需要不同行为,改用不同方法名,或用运行时类型检查(如
instanceof),但后者会绕过泛型安全 - 接口默认方法中使用带边界的泛型参数,也要注意实现类是否意外触发桥接方法冲突
类型擦除后,ArrayList<string></string> 和 ArrayList<integer></integer> 运行时确实是同一个类
没错,JVM 里只有 ArrayList 这一个类,所有泛型信息在编译后全被擦除。这意味着:
-
new ArrayList<string>().getClass() == new ArrayList<integer>().getClass()</integer></string>返回true - 无法在运行时做
if (list instanceof ArrayList<string>)</string>—— 语法直接报错,因为String已不存在 - 反射获取
Field.getGenericType()能看到ParameterizedType,但这只是编译器留在字节码里的元数据,不是运行时类型系统的一部分 - 数组创建受限:
new ArrayList<string>[10]</string>编译失败,必须写成(ArrayList<string>[]) new ArrayList[10]</string>,并伴随 unchecked 警告
泛型最佳实践:哪些地方必须加 @SuppressWarnings("unchecked"),哪些可以避免
不是所有 unchecked 警告都该压制。真正安全且无法避免的情况极少,典型如:
- 从
ObjectInputStream读取泛型集合时,必须强制转型:(List<string>) in.readObject()</string> - 使用
Class.cast()配合泛型工厂方法:clazz.cast(instance),当clazz是泛型类型变量时 - 反射调用返回泛型的方法(如
Method.invoke()),且你知道实际类型
但以下情况**不该**加压制:
- 把原始类型(raw type)赋给泛型变量,比如
List list = new ArrayList(); List<string> safe = list;</string>—— 应该修复源头,启用 -Xlint:unchecked 编译选项暴露问题 - 用
new ArrayList()初始化后立即 add 各种类型,再转型 —— 这是设计缺陷,应改用明确类型或重构为泛型方法 - 为图省事在整段工具方法上加
@SuppressWarnings—— 掩盖了真实类型风险
真正难处理的是跨模块泛型契约:比如 JSON 库反序列化到 List<t></t>,此时类型信息已丢失,要么接受运行时异常,要么用 TypeReference 类保存泛型路径,再配合反射重建结构。










