泛型中的上界限定符`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
。
- 泛型类型可以是