文章目录
- Java基础:泛型核心知识体系(泛型擦除、通配符、上下界限定)
-
- 一、泛型概述
-
- [1.1 定义与本质](#1.1 定义与本质)
- [1.2 核心优势](#1.2 核心优势)
- [1.3 泛型的三种使用形式](#1.3 泛型的三种使用形式)
- [二、泛型擦除(Type Erasure)](#二、泛型擦除(Type Erasure))
-
- [2.1 核心原理](#2.1 核心原理)
- [2.2 擦除规则](#2.2 擦除规则)
- [2.3 擦除带来的问题与解决方案](#2.3 擦除带来的问题与解决方案)
-
- 问题1:无法使用instanceof判断泛型类型
- 问题2:无法创建泛型数组
- 问题3:泛型类型参数不能是基本数据类型
- 问题4:泛型类的静态变量与静态方法不能使用类型参数
- [问题5:桥接方法(Bridge Method)的生成](#问题5:桥接方法(Bridge Method)的生成)
- [2.4 泛型擦除的本质](#2.4 泛型擦除的本质)
- 三、泛型通配符(Wildcard)
-
- [3.1 为什么需要通配符?](#3.1 为什么需要通配符?)
- [3.2 三种通配符类型](#3.2 三种通配符类型)
-
- [1. 无界通配符(Unbounded Wildcard):`?`](#1. 无界通配符(Unbounded Wildcard):
?) - [2. 上界通配符(Upper Bounded Wildcard):`? extends T`](#2. 上界通配符(Upper Bounded Wildcard):
? extends T) - [3. 下界通配符(Lower Bounded Wildcard):`? super T`](#3. 下界通配符(Lower Bounded Wildcard):
? super T)
- [1. 无界通配符(Unbounded Wildcard):`?`](#1. 无界通配符(Unbounded Wildcard):
- 四、上下界限定的深入理解
-
- [4.1 PECS原则(最重要的设计原则)](#4.1 PECS原则(最重要的设计原则))
- [4.2 上下界限定的使用场景对比](#4.2 上下界限定的使用场景对比)
- [4.3 上下界限定的注意事项](#4.3 上下界限定的注意事项)
- 五、常见面试考点与易错点
-
- [5.1 核心面试考点](#5.1 核心面试考点)
- [5.2 常见易错点](#5.2 常见易错点)
- 六、实战应用示例
-
- [6.1 实现一个通用的工具类](#6.1 实现一个通用的工具类)
- [6.2 使用示例](#6.2 使用示例)
- 七、知识体系总结
- Java泛型面试核心考点清单(按面试频率排序)
-
- 一、最高频考点(★★★★★)
-
- [1. 泛型擦除](#1. 泛型擦除)
- [2. PECS原则](#2. PECS原则)
- [3. 三种通配符的区别与使用场景](#3. 三种通配符的区别与使用场景)
- 二、高频考点(★★★★)
-
- [1. 泛型的本质与核心优势](#1. 泛型的本质与核心优势)
- [2. 泛型的三种使用形式](#2. 泛型的三种使用形式)
- [3. `List<Object>`、`List<?>`、`List<? extends Object>`的区别](#3.
List<Object>、List<?>、List<? extends Object>的区别) - [4. 数组协变 vs 泛型不变](#4. 数组协变 vs 泛型不变)
- 三、中频考点(★★★)
-
- [1. 泛型方法与通配符的选择](#1. 泛型方法与通配符的选择)
- [2. 上下界限定的注意事项](#2. 上下界限定的注意事项)
- [3. 为什么不能创建泛型数组?](#3. 为什么不能创建泛型数组?)
- 四、低频考点(★★)
-
- [1. 泛型在集合框架中的应用](#1. 泛型在集合框架中的应用)
- [2. 类型令牌(Type Token)的使用](#2. 类型令牌(Type Token)的使用)
- [3. 泛型的继承关系](#3. 泛型的继承关系)
- 五、常见面试陷阱(★★★★★)
- 六、面试答题技巧
- Java泛型高频面试题+标准答案速记版
-
- 一、必考题(100%会问)
-
- Q1:什么是泛型擦除?为什么Java要使用泛型擦除?
- Q2:解释PECS原则,并举例说明。
- [Q3:`List<Object>`、`List<?>`、`List<? extends Object>`有什么区别?](#Q3:
List<Object>、List<?>、List<? extends Object>有什么区别?) - Q4:为什么不能创建泛型数组?
- 二、高频题(80%会问)
- 三、中频题(50%会问)
- 四、陷阱题(容易答错)
-
- [Q12:`List<? extends Number>`可以添加Integer吗?为什么?](#Q12:
List<? extends Number>可以添加Integer吗?为什么?) - [Q13:`List<? super Integer>`可以读取Integer吗?为什么?](#Q13:
List<? super Integer>可以读取Integer吗?为什么?) - Q14:泛型类的静态方法可以使用类的类型参数吗?为什么?
- [Q12:`List<? extends Number>`可以添加Integer吗?为什么?](#Q12:
- 五、面试答题加分技巧

Java基础:泛型核心知识体系(泛型擦除、通配符、上下界限定)
一、泛型概述
1.1 定义与本质
泛型(Generics)是JDK 5.0引入的参数化类型特性,允许在定义类、接口、方法时使用类型参数(Type Parameter),这些类型参数在使用时才被指定为具体类型。
本质:类型安全的"模板机制",将数据类型作为参数传递,实现代码复用与类型安全的统一。
1.2 核心优势
- 类型安全:编译期类型检查,避免运行时ClassCastException
- 代码复用:一套逻辑可处理多种数据类型
- 可读性:代码意图更清晰,无需频繁强制类型转换
- 解耦:数据类型与业务逻辑分离
1.3 泛型的三种使用形式
| 使用形式 | 语法示例 | 说明 |
|---|---|---|
| 泛型类 | class Box<T> { ... } |
类上定义类型参数 |
| 泛型接口 | interface List<E> { ... } |
接口上定义类型参数 |
| 泛型方法 | <T> T getValue(Box<T> box) { ... } |
方法上独立定义类型参数 |
二、泛型擦除(Type Erasure)
2.1 核心原理
泛型擦除是Java泛型实现的基石 :泛型信息仅存在于编译期,编译完成后所有泛型类型参数都会被擦除,替换为它们的限定类型(无界则为Object)。
设计目的:向后兼容JDK 1.4及之前的版本,保证泛型代码能在旧版JVM上运行。
2.2 擦除规则
-
无界类型参数 :擦除为
Objectjava// 编译前 class Box<T> { private T value; public T getValue() { return value; } } // 编译后(擦除结果) class Box { private Object value; public Object getValue() { return value; } } -
有界类型参数 :擦除为第一个边界类型
java// 编译前 class NumberBox<T extends Number> { private T value; } // 编译后 class NumberBox { private Number value; } -
多边界类型参数:擦除为第一个边界类型
java// 编译前 class ComparableBox<T extends Number & Comparable<T>> { private T value; } // 编译后 class ComparableBox { private Number value; } -
泛型方法:同样遵循上述擦除规则
java// 编译前 public static <T extends Comparable<T>> int compare(T a, T b) { return a.compareTo(b); } // 编译后 public static int compare(Comparable a, Comparable b) { return a.compareTo(b); }
2.3 擦除带来的问题与解决方案
问题1:无法使用instanceof判断泛型类型
java
// 编译错误!泛型类型被擦除,运行时无法获取
if (obj instanceof List<String>) { ... }
// 只能判断原始类型
if (obj instanceof List) { ... }
解决方案:使用Class对象作为类型令牌(Type Token)
java
public <T> boolean isInstance(Object obj, Class<T> clazz) {
return clazz.isInstance(obj);
}
问题2:无法创建泛型数组
java
// 编译错误!泛型类型被擦除,无法保证数组类型安全
List<String>[] lists = new List<String>[10];
// 允许创建原始类型数组,但不安全
List[] lists = new List[10];
解决方案:使用ArrayList代替泛型数组,或使用反射创建
java
// 推荐方案
List<List<String>> lists = new ArrayList<>();
// 反射方案(谨慎使用)
List<String>[] lists = (List<String>[]) Array.newInstance(List.class, 10);
问题3:泛型类型参数不能是基本数据类型
java
// 编译错误!基本类型不能作为泛型参数
List<int> list = new ArrayList<>();
// 必须使用包装类
List<Integer> list = new ArrayList<>();
原因:擦除后类型变为Object,而基本类型不能赋值给Object变量。
问题4:泛型类的静态变量与静态方法不能使用类型参数
java
class Box<T> {
// 编译错误!静态变量属于类,所有实例共享
private static T value;
// 编译错误!静态方法不能使用类的类型参数
public static T getValue() { ... }
// 正确!泛型方法可以有自己的类型参数
public static <E> E getElement(E element) { ... }
}
问题5:桥接方法(Bridge Method)的生成
当泛型类实现泛型接口或继承泛型父类时,编译器会自动生成桥接方法以保证多态性。
java
// 泛型接口
interface Comparable<T> {
int compareTo(T o);
}
// 实现类
class MyInteger implements Comparable<MyInteger> {
@Override
public int compareTo(MyInteger o) {
return 0;
}
}
// 编译后生成的桥接方法
class MyInteger implements Comparable {
public int compareTo(MyInteger o) { ... }
// 桥接方法,保证多态
public int compareTo(Object o) {
return compareTo((MyInteger) o);
}
}
2.4 泛型擦除的本质
Java泛型是**"伪泛型"**,运行时JVM并不知道泛型的存在,所有泛型操作都在编译期完成。这与C++的模板(编译期生成不同类型的类)有本质区别。
三、泛型通配符(Wildcard)
3.1 为什么需要通配符?
泛型不支持协变(Covariance)和逆变(Contravariance),即List<String>不是List<Object>的子类。
java
// 编译错误!泛型不协变
List<Object> list = new ArrayList<String>();
通配符 ?的引入就是为了解决泛型的灵活性问题,允许在不破坏类型安全的前提下,实现泛型类型的多态。
3.2 三种通配符类型
1. 无界通配符(Unbounded Wildcard):?
- 语法:
List<?> - 含义:表示任意类型的泛型
- 适用场景:当方法不依赖于泛型的具体类型时
java
// 打印任意类型的List
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 可以传入任何类型的List
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());
注意 :List<?>不能添加任何元素(除了null),因为编译器无法确定具体类型。
java
List<?> list = new ArrayList<String>();
list.add(null); // 允许
list.add("hello"); // 编译错误!
2. 上界通配符(Upper Bounded Wildcard):? extends T
- 语法:
List<? extends Number> - 含义:表示T或T的子类的泛型
- 适用场景:读取数据(生产者场景)
java
// 计算Number及其子类List的总和
public static double sum(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// 可以传入Number及其子类的List
sum(new ArrayList<Integer>());
sum(new ArrayList<Double>());
sum(new ArrayList<Long>());
特性:
- 可以安全地读取T类型的数据
- 不能添加任何元素(除了null),因为编译器无法确定具体的子类类型
java
List<? extends Number> list = new ArrayList<Integer>();
Number num = list.get(0); // 允许
list.add(123); // 编译错误!
3. 下界通配符(Lower Bounded Wildcard):? super T
- 语法:
List<? super Integer> - 含义:表示T或T的父类的泛型
- 适用场景:写入数据(消费者场景)
java
// 向List中添加Integer及其子类
public static void addIntegers(List<? super Integer> list) {
list.add(123);
list.add(456);
}
// 可以传入Integer及其父类的List
addIntegers(new ArrayList<Integer>());
addIntegers(new ArrayList<Number>());
addIntegers(new ArrayList<Object>());
特性:
- 可以安全地添加T及其子类类型的数据
- 读取数据时只能得到Object类型,因为编译器无法确定具体的父类类型
java
List<? super Integer> list = new ArrayList<Number>();
list.add(123); // 允许
Object obj = list.get(0); // 只能得到Object
Integer num = list.get(0); // 编译错误!
四、上下界限定的深入理解
4.1 PECS原则(最重要的设计原则)
PECS = Producer Extends, Consumer Super
- 生产者(Producer) :提供数据的对象,使用
? extends T(上界通配符) - 消费者(Consumer) :消费数据的对象,使用
? super T(下界通配符)
记忆口诀:"读用extends,写用super,读写都不用通配符"
java
// 生产者:从src中读取数据,src是生产者
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) {
dest.add(t);
}
}
// 使用示例
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();
copy(src, dest); // 正确
4.2 上下界限定的使用场景对比
| 通配符类型 | 语法 | 允许的类型 | 读取操作 | 写入操作 | 适用场景 |
|---|---|---|---|---|---|
| 无界通配符 | ? |
任意类型 | 只能读Object | 只能写null | 不依赖具体类型的操作 |
| 上界通配符 | ? extends T |
T及其子类 | 可读T | 只能写null | 生产者(读取数据) |
| 下界通配符 | ? super T |
T及其父类 | 只能读Object | 可写T及其子类 | 消费者(写入数据) |
| 无通配符 | T |
具体类型T | 可读T | 可写T | 同时读写数据 |
4.3 上下界限定的注意事项
-
通配符不能用于定义泛型类/接口/方法
java// 编译错误!通配符不能作为类型参数 class Box<?> { ... } // 正确!使用类型参数T class Box<T> { ... } -
不能同时使用上下界
java// 编译错误!不能同时使用extends和super List<? extends Number super Integer> list; -
上界通配符的多边界
java// 正确!上界可以有多个,用&分隔 List<? extends Number & Comparable<?>> list; -
下界通配符不能有多个边界
java// 编译错误!下界不能有多个边界 List<? super Integer & Number> list; -
通配符与泛型方法的选择
- 如果方法的参数之间有依赖关系(如返回值类型依赖于参数类型),使用泛型方法
- 如果方法的参数之间没有依赖关系,使用通配符更简洁
java
// 泛型方法:参数和返回值有依赖关系
public static <T> T getFirst(List<T> list) {
return list.get(0);
}
// 通配符:参数之间无依赖关系
public static void printFirst(List<?> list) {
System.out.println(list.get(0));
}
五、常见面试考点与易错点
5.1 核心面试考点
- 什么是泛型擦除?为什么Java要使用泛型擦除?
- 泛型擦除带来了哪些问题?如何解决?
- 什么是桥接方法?为什么需要桥接方法?
- 解释PECS原则,并举例说明。
List<Object>、List<?>、List<? extends Object>有什么区别?- 为什么不能创建泛型数组?
- 泛型方法与通配符的区别与联系?
- 泛型在集合框架中的应用(如List、Map)?
5.2 常见易错点
-
混淆泛型的协变与数组的协变
java// 数组是协变的,允许但不安全 Object[] arr = new String[10]; arr[0] = 123; // 运行时ArrayStoreException // 泛型不协变,编译期就报错 List<Object> list = new ArrayList<String>(); // 编译错误 -
错误地使用通配符进行读写操作
java// 错误:上界通配符不能写入 List<? extends Number> list = new ArrayList<Integer>(); list.add(123); // 编译错误 // 错误:下界通配符不能安全读取 List<? super Integer> list = new ArrayList<Number>(); Integer num = list.get(0); // 编译错误 -
在泛型类中使用静态类型参数
javaclass Box<T> { // 错误:静态变量不能使用类的类型参数 private static T value; } -
使用instanceof判断泛型类型
java// 错误:泛型类型被擦除 if (obj instanceof List<String>) { ... } // 正确:只能判断原始类型 if (obj instanceof List) { ... } -
创建泛型数组
java// 错误:不能创建泛型数组 List<String>[] lists = new List<String>[10]; // 正确:使用List的List List<List<String>> lists = new ArrayList<>();
六、实战应用示例
6.1 实现一个通用的工具类
java
public class CollectionUtils {
// 生产者:获取集合中的最大值
public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException("List is null or empty");
}
T max = list.get(0);
for (T t : list) {
if (t.compareTo(max) > 0) {
max = t;
}
}
return max;
}
// 消费者:将数组元素添加到集合中
public static <T> void addAll(List<? super T> list, T[] array) {
for (T t : array) {
list.add(t);
}
}
// 无界通配符:判断集合是否包含指定元素
public static boolean contains(List<?> list, Object obj) {
return list.contains(obj);
}
}
6.2 使用示例
java
public class Main {
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 3, 5, 2, 4);
System.out.println("Max: " + CollectionUtils.max(intList)); // 5
List<Number> numList = new ArrayList<>();
Integer[] intArray = {1, 2, 3};
CollectionUtils.addAll(numList, intArray);
System.out.println("numList: " + numList); // [1, 2, 3]
List<String> strList = Arrays.asList("a", "b", "c");
System.out.println("Contains 'b': " + CollectionUtils.contains(strList, "b")); // true
}
}
七、知识体系总结
Java泛型的核心是编译期类型检查 与运行期擦除的结合。泛型擦除保证了向后兼容性,但也带来了一些限制;通配符和上下界限定则在不破坏类型安全的前提下,极大地提高了泛型的灵活性。
掌握PECS原则是正确使用通配符的关键:生产者使用extends,消费者使用super。在实际开发中,应根据具体场景选择合适的泛型形式,以实现代码的复用性、可读性和类型安全。
Java泛型面试核心考点清单(按面试频率排序)
一、最高频考点(★★★★★)
1. 泛型擦除
-
核心定义 :Java泛型是编译期特性 ,所有泛型类型参数在编译完成后都会被擦除,替换为它们的限定类型(无界则为Object)。运行时JVM并不知道泛型的存在。
-
设计目的 :向后兼容JDK 1.4及之前版本,保证泛型代码能在旧版JVM上运行。
-
擦除规则 :
- 无界类型参数 → 擦除为
Object - 有界类型参数 → 擦除为第一个边界类型
- 多边界类型参数 → 擦除为第一个边界类型
- 泛型方法 → 遵循上述相同规则
- 无界类型参数 → 擦除为
-
带来的5大核心问题及解决方案 :
问题 解决方案 无法使用 instanceof判断泛型类型使用Class对象作为类型令牌(Type Token) 无法创建泛型数组 使用 ArrayList<List<T>>代替,或反射Array.newInstance()泛型参数不能是基本数据类型 使用对应的包装类 泛型类静态成员不能使用类型参数 静态方法使用独立的泛型方法类型参数 多态性被破坏 编译器自动生成桥接方法(Bridge Method) -
桥接方法:当泛型类实现泛型接口或继承泛型父类时,编译器生成的合成方法,用于保证擦除后多态性依然成立。
2. PECS原则
- 全称:Producer Extends, Consumer Super
- 核心含义 :
- 生产者(提供数据) :使用
? extends T(上界通配符),只能读不能写(除null) - 消费者(消费数据) :使用
? super T(下界通配符),只能写不能安全读(只能读Object)
- 生产者(提供数据) :使用
- 记忆口诀 :读用extends,写用super,读写都不用通配符
- 经典示例 :
Collections.copy(List<? super T> dest, List<? extends T> src)
3. 三种通配符的区别与使用场景
| 通配符类型 | 语法 | 允许的类型 | 读取能力 | 写入能力 | 适用场景 |
|---|---|---|---|---|---|
| 无界通配符 | ? |
任意类型 | 只能读Object | 只能写null | 不依赖具体类型的操作(如打印、判断包含) |
| 上界通配符 | ? extends T |
T及其所有子类 | 可读T类型 | 只能写null | 生产者场景(从集合中读取数据) |
| 下界通配符 | ? super T |
T及其所有父类 | 只能读Object | 可写T及其子类 | 消费者场景(向集合中写入数据) |
二、高频考点(★★★★)
1. 泛型的本质与核心优势
- 本质 :参数化类型,将数据类型作为参数传递,实现"类型安全的模板机制"
- 核心优势 :
- 编译期类型安全 :避免运行时
ClassCastException - 代码复用:一套逻辑处理多种数据类型
- 提高可读性:无需频繁强制类型转换
- 解耦:数据类型与业务逻辑分离
- 编译期类型安全 :避免运行时
2. 泛型的三种使用形式
- 泛型类 :
class Box<T> { ... }(类上定义类型参数) - 泛型接口 :
interface List<E> { ... }(接口上定义类型参数) - 泛型方法 :
<T> T getValue(Box<T> box) { ... }(方法上独立定义类型参数,与类的泛型无关)
3. List<Object>、List<?>、List<? extends Object>的区别
List<Object>:只能存储Object类型,不能接收List<String>等子类泛型List<?>:可以接收任意类型的List,但只能读Object,只能写nullList<? extends Object>:与List<?>完全等价,写法更明确
4. 数组协变 vs 泛型不变
- 数组是协变的 :
String[]是Object[]的子类,但不安全(运行时ArrayStoreException) - 泛型是不变的 :
List<String>不是List<Object>的子类,编译期就报错,更安全
三、中频考点(★★★)
1. 泛型方法与通配符的选择
- 使用泛型方法 :当方法参数之间或参数与返回值之间有类型依赖关系时
- 使用通配符 :当方法参数之间没有类型依赖关系时,代码更简洁
2. 上下界限定的注意事项
- 通配符不能用于定义泛型类/接口/方法(只能用
T等类型参数) - 不能同时使用上下界(
? extends T super U编译错误) - 上界通配符支持多边界(
? extends T & U),下界通配符不支持 - 泛型方法的类型参数也可以有上下界(
<T extends Number>)
3. 为什么不能创建泛型数组?
- 泛型擦除后,数组的类型信息丢失,无法保证运行时类型安全
- 数组是协变的,而泛型是不变的,两者结合会导致类型安全漏洞
四、低频考点(★★)
1. 泛型在集合框架中的应用
- 所有Java集合类都使用了泛型(
List<E>、Set<E>、Map<K,V>等) - 集合的工具类
Collections大量使用了泛型和PECS原则
2. 类型令牌(Type Token)的使用
- 用于在运行时获取泛型类型信息
- 示例:
public <T> T getBean(String name, Class<T> clazz) { ... }
3. 泛型的继承关系
- 泛型类的继承:
class Child<T> extends Parent<T> { ... } - 泛型接口的实现:
class MyList<E> implements List<E> { ... }
五、常见面试陷阱(★★★★★)
-
❌ 错误:
if (obj instanceof List<String>) { ... }✅ 正确:
if (obj instanceof List) { ... } -
❌ 错误:
List<String>[] lists = new List<String>[10];✅ 正确:
List<List<String>> lists = new ArrayList<>(); -
❌ 错误:
List<? extends Number> list = new ArrayList<>(); list.add(123);✅ 正确:
List<Number> list = new ArrayList<>(); list.add(123); -
❌ 错误:
List<? super Integer> list = new ArrayList<>(); Integer num = list.get(0);✅ 正确:
Object obj = list.get(0); -
❌ 错误:
class Box<T> { private static T value; }✅ 正确:
class Box<T> { private T value; }
六、面试答题技巧
- 回答泛型擦除问题时,一定要提到"向后兼容",这是设计的根本原因
- 回答通配符问题时,必须结合PECS原则,并给出具体的读写示例
- 遇到对比题(如数组协变vs泛型不变),先讲定义,再讲区别,最后讲安全性
- 遇到代码题,先指出错误,再说明原因,最后给出正确写法
Java泛型高频面试题+标准答案速记版
(按面试出现频率排序,每题答案控制在30秒-1分钟内,适合考前突击背诵)
一、必考题(100%会问)
Q1:什么是泛型擦除?为什么Java要使用泛型擦除?
标准答案 :
泛型擦除是Java泛型的实现机制:泛型信息仅存在于编译期,编译完成后所有类型参数都会被擦除,替换为它们的限定类型(无界则为Object),运行时JVM并不知道泛型的存在。
设计目的 :向后兼容JDK 1.4及更早版本,保证泛型代码能在旧版JVM上无缝运行,这是Java泛型最核心的设计取舍。
Q2:解释PECS原则,并举例说明。
标准答案 :
PECS = Producer Extends, Consumer Super,是通配符使用的黄金法则:
- 生产者(提供数据) :使用
? extends T(上界通配符),只能安全读取T类型数据,不能写入(除null) - 消费者(消费数据) :使用
? super T(下界通配符),只能安全写入T及其子类数据,读取只能得到Object
经典示例 :Collections.copy(dest, src)方法,src是生产者用? extends T,dest是消费者用? super T。
记忆口诀:读用extends,写用super,读写都不用通配符。
Q3:List<Object>、List<?>、List<? extends Object>有什么区别?
标准答案:
List<Object>:只能存储Object类型,不支持协变 ,不能接收List<String>等任何子类泛型List<?>:无界通配符,可以接收任意类型的List,但只能读Object,只能写nullList<? extends Object>:与List<?>完全等价,只是写法更明确,表达"所有继承自Object的类型"
Q4:为什么不能创建泛型数组?
标准答案 :
两个核心原因:
- 类型安全问题:泛型擦除后数组类型信息丢失,无法保证运行时类型安全
- 协变与不变的冲突 :数组是协变的 (
String[]是Object[]的子类),而泛型是不变的 (List<String>不是List<Object>的子类),两者结合会产生严重的类型安全漏洞
正确替代方案 :使用ArrayList<List<T>>代替泛型数组。
二、高频题(80%会问)
Q5:泛型的本质是什么?有什么核心优势?
标准答案 :
本质 :参数化类型,将数据类型作为参数传递,实现"类型安全的模板机制"。
核心优势:
- 编译期类型安全 :提前发现类型错误,避免运行时
ClassCastException - 代码复用:一套逻辑可处理多种数据类型
- 提高可读性:无需频繁强制类型转换
- 解耦:数据类型与业务逻辑分离
Q6:什么是桥接方法?为什么需要桥接方法?
标准答案 :
桥接方法是编译器在泛型类实现泛型接口或继承泛型父类时,自动生成的合成方法。
作用 :解决泛型擦除后多态性被破坏的问题。例如:
java
// 接口擦除后
interface Comparable { int compareTo(Object o); }
// 实现类
class MyInteger implements Comparable<MyInteger> {
public int compareTo(MyInteger o) { ... }
// 编译器自动生成的桥接方法
public int compareTo(Object o) { return compareTo((MyInteger)o); }
}
Q7:数组协变和泛型不变有什么区别?哪个更安全?
标准答案:
- 数组协变 :
String[]是Object[]的子类,允许赋值,但不安全 ,会在运行时抛出ArrayStoreException - 泛型不变 :
List<String>不是List<Object>的子类,编译期就报错,更安全
结论:泛型不变比数组协变更安全,这也是Java泛型设计为不变的重要原因。
Q8:泛型方法和通配符有什么区别?什么时候用哪个?
标准答案:
- 泛型方法 :使用
<T>定义独立的类型参数,适用于方法参数之间或参数与返回值之间有类型依赖关系的场景 - 通配符 :使用
?表示未知类型,适用于方法参数之间没有类型依赖关系的场景,代码更简洁
示例:
- 泛型方法:
public <T> T getFirst(List<T> list)(返回值依赖参数类型) - 通配符:
public void printFirst(List<?> list)(无类型依赖)
三、中频题(50%会问)
Q9:泛型擦除带来了哪些问题?如何解决?
标准答案:
| 问题 | 解决方案 |
|---|---|
无法使用instanceof判断泛型类型 |
使用Class对象作为类型令牌 |
| 无法创建泛型数组 | 使用ArrayList<List<T>>代替 |
| 泛型参数不能是基本数据类型 | 使用对应的包装类 |
| 泛型类静态成员不能使用类型参数 | 静态方法使用独立的泛型方法类型参数 |
| 多态性被破坏 | 编译器自动生成桥接方法 |
Q10:为什么不能用instanceof判断泛型类型?
标准答案 :
因为泛型擦除后,运行时JVM只知道原始类型,不知道具体的泛型参数类型。例如List<String>和List<Integer>在运行时都是List类型,无法区分。
正确写法 :只能判断原始类型if (obj instanceof List)。
Q11:泛型有哪三种使用形式?各举一个例子。
标准答案:
- 泛型类 :
class Box<T> { private T value; } - 泛型接口 :
interface List<E> { boolean add(E e); } - 泛型方法 :
public static <T> T getValue(Box<T> box) { return box.value; }
四、陷阱题(容易答错)
Q12:List<? extends Number>可以添加Integer吗?为什么?
标准答案 :
不可以 。因为List<? extends Number>可以指向List<Integer>、List<Double>等任意Number子类的List。如果允许添加Integer,那么当它指向List<Double>时就会出现类型错误。
唯一可以添加的元素是null,因为null是所有引用类型的实例。
Q13:List<? super Integer>可以读取Integer吗?为什么?
标准答案 :
不可以安全读取 。因为List<? super Integer>可以指向List<Integer>、List<Number>、List<Object>等任意Integer父类的List。读取时编译器无法确定具体类型,只能返回Object。
正确写法 :Object obj = list.get(0);
Q14:泛型类的静态方法可以使用类的类型参数吗?为什么?
标准答案 :
不可以。因为静态方法属于类本身,而泛型类的类型参数是在创建实例时才确定的。静态方法在实例创建前就可以调用,此时类型参数还没有被指定。
正确写法 :静态方法可以定义自己的泛型类型参数:public static <T> T getElement(T element)。
五、面试答题加分技巧
- 回答任何泛型问题时,只要涉及擦除,一定要提到"向后兼容",这是Java泛型所有设计取舍的根源
- 回答通配符问题时,必须结合PECS原则,并给出具体的读写示例
- 遇到代码题,先指出错误,再说明原因,最后给出正确写法
- 回答对比题时,先讲定义,再讲区别,最后讲安全性