什么是通配符?
通配符(Wildcard)是泛型中的一种特殊类型,它用?表示"未知类型"。与泛型类/方法的类型参数T不同,通配符主要用于方法参数,用来实现更灵活的赋值兼容性。
生活比喻
通配符就像一张"职位申请表"上的空白——不指定具体是谁,但知道是某类人。比如"招聘对象:计算机相关专业"(extends),或者"招收:能教书的"(super)。通配符就是这种模糊但有边界的描述。
三种通配符详解
| 通配符 | 名称 | 含义 | 可读 | 可写 |
|---|---|---|---|---|
| ? | 无界通配符 | 未知类型 | ✓(返回Object) | ✗ |
| ? extends T | 上界通配符 | T或T的子类 | ✓(返回T) | ✗ |
| ? super T | 下界通配符 | T或T的父类 | ✓(返回Object) | ✓(可写入T) |
无界通配符(?)
?表示任意类型。当你只需要读取集合元素(当Object处理)而不需要写入时使用。
import java.util.List;
import java.util.ArrayList;
public class UnboundedWildcardDemo {
public static void main(String[] args) {
List ints = new ArrayList<>();
ints.add(1);
printList(ints);
List strings = new ArrayList<>();
strings.add("Hello");
printList(strings);
}
// 使用无界通配符 - 可以接受任何类型的List
public static void printList(List> list) {
for (Object item : list) {
System.out.println("元素: " + item);
}
// 写入会编译错误:
// list.add("test"); // 错误!
// list.add(1); // 错误!
}
}
上界通配符(? extends T)
? extends T表示T或T的子类。你可以把T的子类型集合赋给它。由于不知道具体是哪个子类,写入会很危险,所以只读不写。
理解"只读"
想象你有一个"水果盘"(Fruit[]),你只知道里面装的是水果(Fruit的子类),但不知道是苹果还是香蕉。你可以读取任何一个——但你不能往里面放东西,因为你不知道那个位置原来放的是什么类型的盘子。
import java.util.List;
import java.util.ArrayList;
public class UpperBoundWildcardDemo {
public static void main(String[] args) {
List ints = Arrays.asList(1, 2, 3, 4, 5);
List doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("Integer列表和: " + sumOfList(ints));
System.out.println("Double列表和: " + sumOfList(doubles));
}
// 上界通配符 - 接受Number或Number子类的列表
public static double sumOfList(List extends Number> list) {
double sum = 0;
for (Number n : list) {
sum += n.doubleValue(); // 可以安全读取为Number
}
// 写入会编译错误:
// list.add(Integer.valueOf(1)); // 错误! 可能是Double列表
return sum;
}
}
下界通配符(? super T)
? super T表示T或T的父类。这时你可以安全地写入T及其子类,因为无论如何,写入的元素都会被当作T或T的父类。但读取时只能当Object处理。
理解"可写"
想象你有一个"容器",只要它能装"数字"(Number)就行。你往里面放Integer、Double都可以,因为它们都是Number。但如果容器声明的是Object,你就不能保证取出来的是特定类型——只能当Object处理。
import java.util.List;
import java.util.ArrayList;
public class LowerBoundWildcardDemo {
public static void main(String[] args) {
List numbers = new ArrayList<>();
List
PECS原则
PECS(Producer-Extends, Consumer-Super)是记住何时用extends何时用super的最简单方法:
PECS原则详解:
┌────────────────────────────────────────────────────────────┐
│ │
│ 如果你的泛型变量是"生产者"(往外提供数据)→ 用 extends │
│ 如果你的泛型变量是"消费者"(往里写入数据)→ 用 super │
│ │
│ 记忆口诀:"生产者用extends,消费者用super" │
│ "Extends提供数据,Super消费数据" │
└────────────────────────────────────────────────────────────┘
PECS示例:完整的鸳鸯锅
import java.util.List;
import java.util.ArrayList;
public class PECSDemo {
public static void main(String[] args) {
// 生产者场景 - 需要往外读取数据 → 用 extends
List ints = Arrays.asList(1, 2, 3, 4, 5);
List doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("Integer最大值: " + max(ints));
System.out.println("Double最大值: " + max(doubles));
// 消费者场景 - 需要往里写入数据 → 用 super
List numbers = new ArrayList<>();
List
什么时候用通配符而不是泛型方法?
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 只需要读 | ? extends T | 灵活接受T及其子类 |
| 只需要写 | ? super T | 灵活接受T及其父类 |
| 既读又写 | <T>泛型方法 | 两边都需要确定类型 |
| 只做Object操作 | ? | 不需要知道具体类型 |
常见错误
错误1:同时使用extends和super
List extends Comparable & Serializable> - 这不是同时用,而是多重限定(一个上界同时实现多个接口),是合法的。
错误2:在泛型变量中混用读写
如果既想读又想写,不要用通配符,用泛型方法:public static
小结
- ? extends T:读取T及其子类,写入会危险(只读)
- ? super T:写入T及其父类,读取只能当Object(可写)
- ?(无界):只读Object,操作最受限
- PECS原则:生产者用extends,消费者用super
- 泛型方法适合既读又写的场景