Java 泛型

返回 面试 · 速查全集 Java常见面试题

编译期类型安全,消除强转;运行时因类型擦除List<String>List<Integer> 之分。详解 泛型


为什么用泛型

无泛型有泛型
List list = ...; String s = (String) list.get(0);List<String> list = ...; String s = list.get(0);
运行时 ClassCastException编译期检查类型

泛型类 / 方法 / 接口

public class Box<T> { private T value; }
 
public static <T> List<T> repeat(T item, int n) { ... }
 
public interface Mapper<I, O> { O map(I in); }

方法级 <T> 与类的 <T> 相互独立


通配符与 PECS

写法场景
? extends T❌(除 null)生产者 Extends
? super T❌(除 Object)消费者 Super
T既读又写
// Collections.copy 典型 PECS
void copy(List<? super T> dest, List<? extends T> src)

记忆extends 上界只读;super 下界只写。


类型擦除

编译后泛型信息被擦除:

List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass();  // true,都是 ArrayList
擦除规则示例
无界 TObject
T extends NumberNumber
编译器插入强转(String) list.get(0)

擦除带来的限制

// ❌ 不能 new T()
// ❌ 不能 new T[]
// ❌ 静态字段不能用类的类型参数 static T x;
// ❌ instanceof List<String>
// ✅  instanceof List<?>

泛型与继承

List<Integer> ints = new ArrayList<>();
// List<Number> nums = ints;  // ❌ 编译错误
 
List<? extends Number> nums = ints;  // ✅ 只读视角

不变性List<A>List<B> 无父子关系,即使 A 是 B 子类。


运行时如何拿泛型类型

擦除后默认拿不到 List<String> 的实际参数,但声明处信息可保留:

// 匿名子类保留 ParameterizedType
Type type = new TypeReference<List<String>>() {}.getType();
 
// 父类泛型参数
Type t = getClass().getGenericSuperclass();

MyBatis BaseMapper<T>、Jackson TypeReference、Spring ResolvableType 均利用此技巧。见 Java反射


常见面试题

Q:泛型和 Object 强转区别?
A:泛型在编译期检查,错误提前暴露;Object 需每次手动 cast。

Q:List<?>List<Object>
A:List<?> 未知元素类型,几乎不能 add(除 null);List<Object> 可 add 任意 Object,二者不等价。

Q:能否 List<String>[] arr = new List<String>[10]
A:不能;数组协变 + 擦除会导致堆污染,只允许 (List<String>[]) new List<?>[10] 等不安全写法(不推荐)。

Q:Java 泛型和 C# 泛型?
A:Java 擦除,运行时无 reified 类型;C# 保留运行时泛型信息(面试了解即可)。


相关笔记