泛型中的上界限定符`extends`和下界限定符`super`各自的作用是什么,它们之间有何不同?
参考回答**
在 Java 的泛型中,extends 和 super 是泛型通配符的 上界限定符 和 下界限定符,它们的作用是限制泛型参数的类型范围,从而在方法或类中对泛型参数进行更加灵活的操作。
? extends T:上界通配符,表示泛型类型可以是 T或 T的子类(包括 T本身)。- 主要用于只读操作。
? super T:下界通配符,表示泛型类型可以是 T或 T的父类(包括 T本身)。- 主要用于写入操作。
它们的区别在于:
? extends T:适用于协变(读取时可以保证是某种类型及其子类型)。? super T:适用于逆变(写入时可以保证是某种类型及其父类型)。
详细讲解与拓展
1. ? extends T 上界通配符
作用:限制泛型参数必须是类型 T 或 T 的子类。
- 常用于只读场景,因为可以安全地读取为
T类型。 - 不允许往泛型容器中添加
T或其子类的元素(除了null),因为类型不确定。
示例:计算总和 假设有一个方法需要接受一个存储数字的泛型列表并计算总和:
public double sum(List<? extends Number> list) {
double sum = 0;
for (Number num : list) {
sum += num.doubleValue(); // 可以安全读取为 Number
}
return sum;
}
使用场景:
- 允许传入
List<Integer>、List<Double>等Number的子类列表。 - 限制:不能往
list中添加元素,因为编译器无法确定确切的子类型。
List<Integer> intList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.1, 2.2, 3.3);
System.out.println(sum(intList)); // 输出 6.0
System.out.println(sum(doubleList)); // 输出 6.6
注意:在使用
? extends T时,可以从列表中读取元素,但无法向列表中添加元素(除了null)。
2. ? super T 下界通配符
作用:限制泛型参数必须是类型 T 或 T 的父类。
- 常用于写入场景,因为可以安全地向泛型容器中添加
T类型或其子类型的元素。 - 不保证读取的类型,因为泛型参数可能是
T的父类,读取时只能保证是Object类型。
示例:添加元素 假设有一个方法需要向泛型列表中添加 Integer 或其子类的值:
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
使用场景:
- 允许传入
List<Integer>、List<Number>、List<Object>。 - 限制:从列表中读取元素时,编译器只能保证它是
Object类型。
List<Number> numList = new ArrayList<>();
addIntegers(numList);
System.out.println(numList); // 输出 [1, 2, 3]
注意:在使用
? super T时,可以向列表中写入T或T的子类型的元素,但从列表中读取元素时,只能保证读取到Object。
3. ? extends T 和 ? super T 的对比
| 特性 | ? extends T |
? super T |
|---|---|---|
| 适用场景 | 读取:从列表中读取数据,保证是 T 或其子类 |
写入:向列表中添加数据,保证是 T 或其子类 |
| 类型范围 | T 或 T 的子类 |
T 或 T 的父类 |
| 读取操作 | 可以读取为 T |
只能读取为 Object |
| 写入操作 | 不能写入(除了 null) |
可以写入 T 或其子类 |
| 典型用途 | 协变:传入子类时适合只读场景 | 逆变:传入父类时适合只写场景 |
示例代码对比:
// 使用 ? extends T
public void processList(List<? extends Number> list) {
Number num = list.get(0); // 可以读取为 Number
// list.add(1); // 编译错误:无法确定可以添加的具体类型
}
// 使用 ? super T
public void processList(List<? super Integer> list) {
list.add(1); // 可以安全写入 Integer 类型
Object obj = list.get(0); // 读取时只能作为 Object
}
4. 常见应用场景
(1) 上界通配符:? extends T
- 读取数据:确保从集合中读取的元素至少是
T类型。 - 典型应用:处理需要从泛型集合中读取数据的逻辑,如汇总计算、排序等。
示例:打印列表内容
public void printList(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
(2) 下界通配符:? super T
- 写入数据:确保向集合中写入的元素是
T类型或其子类。 - 典型应用:处理需要向泛型集合中添加数据的逻辑,如填充数据、初始化集合等。
示例:向列表中添加数据
public void fillList(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
5. PECS 原则
Java 泛型设计中的一个经典原则是 PECS(Producer Extends, Consumer Super):
- Producer Extends:如果你需要从集合中获取数据,使用
? extends T。 - Consumer Super:如果你需要向集合中添加数据,使用
? super T。
示例:
- 使用
? extends T:适合数据的生产者,如List<? extends Number>。 - 使用
? super T:适合数据的消费者,如List<? super Integer>。
6. 总结
? extends T(上界通配符):- 泛型类型可以是
T或其子类。 - 用途:适用于只读场景。
- 限制:不允许写入元素(除了
null)。
- 泛型类型可以是
? super T(下界通配符):- 泛型类型可以是
T或其父类。 - 用途:适用于只写场景。
- 限制:读取时只能作为
Object。
- 泛型类型可以是