在 Java 泛型中,? 被称为通配符(Wildcard)。它用来表示"未知的类型",主要在声明变量或参数时使用,以增加代码的灵活性。
三种主要形式
1. 无界通配符(Unbounded Wildcard):<?>
表示"可以是任何类型",但因为你不知道具体是什么类型,只能从中读取 Object 类型,不能往里添加元素 (除了 null)。
java
public void printList(List<?> list) {
Object obj = list.get(0); // ✅ 可以读,返回 Object
// list.add("hello"); // ❌ 编译错误!不能添加任何元素
list.add(null); // ✅ 特例,可以加 null
}
适用场景:不关心具体类型,仅用 Object 提供的方法就能处理。比如打印所有元素、判断是否为空。
2. 上界通配符(Upper Bounded Wildcard):<? extends T>
表示"类型是 T 或 T 的子类"。适用于读取场景------从结构中获取数据时,可以保证得到 T 类型。
java
public double sum(List<? extends Number> numbers) {
double total = 0.0;
for (Number n : numbers) { // ✅ 可以读,确保是 Number 或其子类
total += n.doubleValue();
}
// numbers.add(1); // ❌ 编译错误!不能添加具体元素
return total;
}
// 调用
sum(List.of(1, 2, 3)); // Integer 子类
sum(List.of(1.0, 2.5, 3.8)); // Double 子类
限制 :只允许读取 ,不能写入(除了 null)。因为编译器不知道具体的子类型是什么(可能是 Integer、Double 等),无法保证类型安全。
3. 下界通配符(Lower Bounded Wildcard):<? super T>
表示"类型是 T 或 T 的父类"。适用于写入场景------可以向结构中添加 T 类型的对象,但读取时只能得到 Object。
java
public void addNumbers(List<? super Integer> list) {
list.add(100); // ✅ 可以添加 Integer
list.add(200);
// Integer num = list.get(0); // ❌ 编译错误!不能直接读成 Integer
Object obj = list.get(0); // ✅ 只能作为 Object 读取
}
// 调用
List<Number> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
addNumbers(list1); // Number 是 Integer 的父类 ✅
addNumbers(list2); // Object 也是父类 ✅
// List<Integer> list3 = ... // ❌ Integer 不是 Integer 的父类
规则:
- 可以写:T 及 T 的子类(但实践中通常只写 T 本身)
- 可以读:只能当作 Object 来读(因为父类型可能是 Object、Number 等不确定)
经典使用场景:PECS 原则
PECS = Producer Extends, Consumer Super
这是 Joshua Bloch 在《Effective Java》中提出的记忆口诀:
- Producer (生产者) :如果参数提供数据给你读 → 用
<? extends T>上界 - Consumer (消费者) :如果参数消费你提供的数据(你往里写)→ 用
<? super T>下界
java
// Producer: 从集合中拷贝数据出去(读)
public void copy(List<? extends Number> source, List<? super Number> dest) {
for (Number n : source) { // source 是生产者,用 extends
dest.add(n); // dest 是消费者,用 super
}
}
常见误区对比
| 写法 | 含义 | 能读 | 能写 |
|---|---|---|---|
List<T> |
具体类型 T | T 类型 | T 类型 |
List<?> |
未知类型 | Object | 只能 null |
List<? extends Number> |
某个 Number 子类 | Number | 不能(除 null) |
List<? super Integer> |
某个 Integer 父类 | Object | Integer 及子类 |
关键区别:<T> 与 <?>
java
// 类型参数 T:可以在多处保持一致
public <T> void swap(List<T> list, int i, int j) {
T temp = list.get(i); // 类型确定
list.set(i, list.get(j));
list.set(j, temp);
}
// 通配符 ?:不关心彼此是否一致
public void print(List<?> list) {
// 没法保证 list.get(0) 和 list.get(1) 是同一类型,但这里不需要
}
原则:
- 声明泛型类/方法 时用
<T>(类型参数) - 声明变量/参数 类型时用
<?>(通配符)
实际应用举例
java
// ✅ 好的用法:灵活接收各类集合
public void sort(List<? extends Comparable<?>> list) { ... }
// ✅ 结合集合工具类
Collections.copy(dest, src); // dest: <? super T>, src: <? extends T>
// ❌ 错误:不能同时有上界和下界
// List<? extends Number super Integer> // 语法不支持
// ❌ 错误:不能用在 new 泛型实例上
// List<?> list = new ArrayList<?>(); // 编译错误
List<?> list = new ArrayList<String>(); // ✅ 但右边需具体类型
总结
<?>:我不知道是什么类型<? extends T>:它是 T 或 T 的子类(主要用来读)<? super T>:它是 T 或 T 的父类(主要用来写)- PECS :
extends适合 Producer(读),super适合 Consumer(写) - 通配符让 API 更灵活,但也会限制能做的操作