类型擦除与PECS是Java泛型同一设计逻辑的两面:擦除确保向后兼容但运行时丢失类型信息,PECS则通过通配符在擦除限制下保障类型安全的读写操作。

Java 泛型的类型擦除和 PECS 原则是同一套设计逻辑下的两个侧面:擦除是实现约束,PECS 是使用策略。它们共同服务于一个目标——在保留向后兼容的前提下,让泛型既安全又灵活。
类型擦除决定了“运行时看不到类型”
编译器把 List<string></string>、List<integer></integer> 全部擦成原始类型 List,字节码里没有泛型信息。这意味着:
- JVM 不知道你声明的是什么具体类型,所以无法在运行时做类型检查或实例化
T - 所有泛型方法调用,编译器必须在调用点插入隐式强转(比如
(String) list.get(0)),来模拟类型安全 - 泛型类不能被反射直接获取真实类型参数,
list.getClass()永远返回ArrayList.class,不是ArrayList<string>.class</string>
PECS 是对擦除后“类型不可知”的务实应对
因为擦除导致泛型类型在运行时退化为原始类型,而 Java 又不允许协变(List<string></string> 不是 List<object></object> 的子类),所以需要通配符来放宽参数边界。PECS 就是在这种限制下总结出的安全读写规则:
-
Producer Extends:如果只从集合读取(生产者),用
? extends T。例如void printNames(List extends Person> people)—— 你能安全地把每个元素当Person用,但不能往里加任何东西(除了null),因为编译器不知道具体是Student还是Teacher -
Consumer Super:如果只往集合写入(消费者),用
? super T。例如void addStudents(List super Student> list)—— 你可以放心加Student或其子类,但取出来只能当Object用,因为编译器只知道它至少能装Student,上限未知
擦除 + PECS = 编译期安全的最大化妥协
类型擦除让泛型无法像 C# 那样在运行时保留完整类型信息,也就无法支持真正的泛型数组、new T() 或类型内省。PECS 则是在这个硬性限制下,通过上下界限定,在方法签名层面重建类型契约:
立即学习“Java免费学习笔记(深入)”;
- 它不试图绕过擦除,而是接受擦除,并用编译器推导替代运行时判断
- 所有
? extends和? super的合法性检查,都在编译期完成;一旦通过,运行时靠擦除后的原始类型 + 插入的强制转换兜底 - 比如
Collection extends Number> nums = new ArrayList<integer>()</integer>,编译器确认Integer是Number子类,就允许赋值;运行时仍按Collection处理,但每次get()都自动补上(Number)转换
不复杂但容易忽略
理解擦除,你就明白为什么泛型不能用于静态上下文、不能做 instanceof T、不能创建泛型数组;理解 PECS,你就知道什么时候该用 extends、什么时候该用 super——不是凭感觉,而是由擦除带来的类型不确定性所决定的必然选择。


















