Java泛型是编译期特性,运行时通过类型擦除替换为原始类型或上界(如Object/Number),导致List<String>与List<Integer>运行时均为List、无法new T[]、instanceof T非法;通配符? extends T用于安全读取,? super T用于安全写入;桥接方法解决多态,静态泛型和泛型异常因擦除失效。

练泛型高级面试题,核心不是背答案,而是理解“为什么这么设计”和“不这么写会出什么问题”。重点落在擦除机制、边界限制、通配符语义、以及与继承/重载/静态成员的冲突上。
紧扣类型擦除反推行为
Java泛型是“编译期特性”,运行时只剩原始类型。所有高级题几乎都绕不开这点:
- 问“
List<String>和List<Integer>运行时是否同一类型?”——答:是,都是List,擦除后无区别 - 问“为什么不能
new T[10]?”——答:T 擦除为 Object,但数组创建需明确运行时类型,JVM 不允许创建Object[]后再当成String[]用(会破坏类型安全) - 问“
instanceof T为什么不合法?”——答:T 在运行时不存在,无法做类型判断
吃透通配符的三种写法和适用场景
别只记符号,要能对应实际需求:
-
?(无界):只读操作,比如void print(List<?> list),只要能遍历就行 -
? extends T(上界):取数据安全,比如Number max(List<? extends Number> nums),保证 get 出来一定是 Number 或子类 -
? super T(下界):存数据安全,比如void addInts(List<? super Integer> dest),可以放心 add(Integer),因为 dest 至少能装 Integer 及其父类(如 Number、Object)
典型陷阱题:“List<String> 能否赋值给 List<? extends Object>?”——可以;但“能否赋值给 List<? super String>?”——也可以;而“能否赋值给 List<Object>?”——不行,这是常见错误点。
立即学习“Java免费学习笔记(深入)”;
动手写桥接方法和类型冲突案例
面试官爱考“为什么这段代码编译失败”,背后常涉及泛型与继承/重载的交互:
- 写一个泛型类
Node<T>,再写子类MyNode extends Node<Integer>,观察编译后生成的桥方法(bridge method) - 尝试定义
static <T> T getValue()—— 编译失败,因为静态上下文无法绑定实例化的 T - 尝试让类同时实现
Comparable<String>和Comparable<Integer>—— 编译失败,擦除后都是Comparable,接口重复
对比真泛型语言建立认知锚点
拿 C# 或 Rust 的泛型对比 Java,能快速定位“伪泛型”的代价和权衡:
- C# 泛型在运行时保留类型信息,支持
typeof(T)、可创建T[]、可对值类型做零开销泛型 - Java 选择擦除,是为了让
ArrayList新旧代码二进制兼容——老 jar 包不用改就能跑在 JDK 5+ 上 - 所以 Java 泛型不能支持基本类型(得用包装类)、不能做运行时类型分支(
if (T == String.class)不成立)
练到这个程度,遇到“为什么泛型不能用于异常类型”“为什么不能用泛型声明枚举常量”这类题,你自然能从擦除 + JVM 规范 + 设计取舍三层讲清楚。


















