Java 泛型通配符 详细讲解
泛型通配符使用 ? 表示未知类型 ,主要用于解决泛型参数的多态兼容 问题,分为无界通配符、上界通配符、下界通配符三类,同时结合读写规则、使用场景、面试考点逐一说明。
前置结论:
List<String>不是List<Object>的子类,泛型本身不支持协变;- 通配符就是为了打破泛型集合无法向上兼容的限制;
- 核心记忆:上界读优先、下界写优先、无界只读。
一、为什么需要通配符?
先看一个报错案例,理解痛点:
java
import java.util.ArrayList;
import java.util.List;
public class Test {
// 要求接收 List<Object>
public static void printList(List<Object> list) {
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
strList.add("Java");
// 编译报错:List<String> 不能赋值给 List<Object>
printList(strList);
}
}
原因:泛型没有多态 ,List<String> 和 List<Object> 是两个完全独立的类型,不存在父子关系。
此时就需要通配符 ? 来表示"任意未知类型",实现参数兼容。
二、无界通配符 <?>
1. 定义
<?> 代表完全未知的任意类型 ,不做任何类型限制。
语法:集合<?> / 类型<?>
2. 适用场景
当只读取集合元素、不向集合写入数据,且不关心集合具体存储什么类型时使用。
3. 读写规则(重点)
- 读 :可以读取元素,读取后类型统一为
Object; - 写 :除了
null,不能写入任何对象(编译器无法判断目标类型,禁止写入,避免类型安全问题)。
4. 代码示例
java
import java.util.ArrayList;
import java.util.List;
public class WildcardNone {
// 无界通配符:接收任意类型的 List
public static void show(List<?> list) {
// 读取:元素转为 Object
for (Object obj : list) {
System.out.println(obj);
}
// 写入:只能写 null,其余全部编译报错
list.add(null);
// list.add("abc"); // 报错
// list.add(123); // 报错
}
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
strList.add("泛型");
List<Integer> intList = new ArrayList<>();
intList.add(666);
show(strList);
show(intList);
}
}
5. 总结
- 用途:通用遍历、查看数据;
- 特点:只读为主,禁止写入(null除外)。
三、上界通配符 <? extends T>
1. 定义
限定类型为 T 本身 或 T 的子类 ,也叫上限通配符 。
语法:<? extends 父类/接口>
2. 继承关系示例
假设类继承:Number ← Integer、Number ← Double
List<? extends Number>可以接收:List<Number>、List<Integer>、List<Double>
3. 读写规则(核心考点)
- 读操作 :允许读取,取出的元素可以赋值给 父类
T(安全); - 写操作 :完全禁止写入非 null 数据 。 原因:编译器无法确定集合真实子类型。比如
List<? extends Number>可能是List<Integer>,也可能是List<Double>,强行写入会引发类型异常。
4. 代码示例
java
import java.util.ArrayList;
import java.util.List;
public class WildcardExtends {
// 上界通配符:只接收 Number 及其子类
public static void printNum(List<? extends Number> list) {
// 读取:安全,用 Number 接收
for (Number num : list) {
System.out.println(num);
}
// 写入:全部报错
// list.add(100);
// list.add(3.14);
list.add(null); // 仅允许 null
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(10);
List<Double> douList = new ArrayList<>();
douList.add(3.14);
printNum(intList);
printNum(douList);
}
}
5. 适用场景
从集合中读取数据,元素有统一父类,典型场景:数据查询、遍历、统计。
四、下界通配符 <? super T>
1. 定义
限定类型为 T 本身 或 T 的父类 ,也叫下限通配符 。
语法:<? super 子类>
2. 继承关系示例
依旧以 Integer → Number → Object 为例:
List<? super Integer>可以接收:List<Integer>、List<Number>、List<Object>
3. 读写规则(核心考点)
- 写操作 :允许写入
T及其子类对象(安全); - 读操作 :可以读取,但取出的元素只能用
Object接收(无法确定具体父类)。
4. 代码示例
java
import java.util.ArrayList;
import java.util.List;
public class WildcardSuper {
// 下界通配符:接收 Integer 及其父类
public static void addNum(List<? super Integer> list) {
// 写入:允许 Integer / Integer 子类
list.add(100);
list.add(200);
// 读取:只能用 Object 接收
for (Object obj : list) {
System.out.println(obj);
}
// Integer num = list.get(0); // 编译报错
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<Number> numList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
addNum(intList);
addNum(numList);
addNum(objList);
}
}
5. 适用场景
向集合中写入数据,典型场景:数据填充、元素添加、批量存入对象。
五、三种通配符对比表
| 通配符格式 | 含义 | 可读 | 可写 | 典型使用场景 |
|---|---|---|---|---|
<?> 无界 |
任意未知类型 | ✅ 读为 Object | ❌ 仅可写 null | 通用遍历、查看数据 |
<? extends T> 上界 |
T 或 T 的子类 | ✅ 读为 T | ❌ 仅可写 null | 读取数据、查询、遍历 |
<? super T> 下界 |
T 或 T 的父类 | ✅ 只能读为 Object | ✅ 可写入 T/子类 | 写入数据、填充集合 |
六、高频考点 & 拓展知识
1. PECS 原则(面试必问)
PECS:Producer Extends, Consumer Super
- 生产者(只往外读数据) :使用
<? extends T> - 消费者(只往里写数据) :使用
<? super T> - 既要读又要写:不要使用通配符 ,直接使用确定泛型
<T>
2. 通配符不能和泛型边界混用的误区
- 可以组合:
public <T extends Number> void test(List<T> list)(泛型方法边界) - 区分:泛型边界
<T extends>是定义类型范围;通配符<? extends>是接收参数范围。
3. 通配符与泛型数组
Java 不支持泛型数组,通配符数组也有诸多限制,日常开发尽量避免。
4. 通配符不能用于泛型方法返回值
java
// 不推荐、可读性极差,业务中禁止使用
public List<?> getList(){...}
返回值尽量使用确定泛型 <T>,不要用通配符。
七、综合实战案例(PECS 落地)
需求:实现一个工具方法,将源集合元素拷贝到目标集合
java
import java.util.ArrayList;
import java.util.List;
public class CopyDemo {
/**
* 拷贝集合
* 源集合:只读取 → Producer → <? extends T>
* 目标集合:只写入 → Consumer → <? super T>
*/
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) {
dest.add(t);
}
}
public static void main(String[] args) {
List<Integer> src = new ArrayList<>();
src.add(1);
src.add(2);
List<Number> dest1 = new ArrayList<>();
List<Object> dest2 = new ArrayList<>();
copy(src, dest1);
copy(src, dest2);
System.out.println(dest1);
System.out.println(dest2);
}
}
这是 JDK 中 Collections.copy 的核心设计思想,完美体现 PECS 原则。
八、总结
- 通配符
?解决泛型集合无法多态兼容的问题; - 三大类型:
<?>:任意类型,只读;<? extends T>:上界(子类),适合读;<? super T>:下界(父类),适合写;
- 牢记 PECS 原则:读用 extends,写用 super,读写都用则不用通配符;
- 核心限制:除
null外,上界/无界通配符基本禁止写入,避免类型转换异常。