什么是泛型中的限定通配符和非限定通配符 ?
参考回答
在 Java 的泛型中,通配符用于定义一个范围不明确的类型参数。主要分为以下两类:
- 非限定通配符 (
?
):表示可以接受任何类型。
- 例如:
List<?>
可以表示List<String>
、List<Integer>
等任意类型的列表。
- 限定通配符:
- 上界通配符
(? extends T):表示可以接受类型 T或 T的子类。
- 例如:
List<? extends Number>
可以接受List<Integer>
或List<Double>
。
- 例如:
- 下界通配符
(? super T):表示可以接受类型 T或 T的父类。
- 例如:
List<? super Integer>
可以接受List<Integer>
或List<Number>
。
- 例如:
通配符的选择取决于代码的需求,尤其是对泛型数据的读写操作。
详细讲解与拓展
1. 非限定通配符 ?
?
表示未知的类型,适用于不关心具体类型,只需要在逻辑上统一处理的场景。它通常用于只读操作。
示例:
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
List<String> stringList = List.of("A", "B", "C");
List<Integer> intList = List.of(1, 2, 3);
printList(stringList); // 输出 A, B, C
printList(intList); // 输出 1, 2, 3
注意:在使用
?
时,你无法往集合中添加元素(除了null
),因为它不知道?
具体代表什么类型。
2. 限定通配符
(1) 上界通配符 ? extends T
? extends T
表示类型的上界是T
,可以是T
或T
的子类。- 常用于只读操作,因为可以安全地读取为
T
类型,但无法向其中添加元素。
示例:
public double sumOfNumbers(List<? extends Number> list) {
double sum = 0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
List<Integer> intList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.1, 2.2, 3.3);
System.out.println(sumOfNumbers(intList)); // 输出 6.0
System.out.println(sumOfNumbers(doubleList)); // 输出 6.6
扩展知识点:
上界通配符的设计是为了让泛型支持协变(Covariance)。比如List<Integer>
和List<Double>
都可以被认为是List<? extends Number>
,即可以安全地读取为Number
。
(2) 下界通配符 ? super T
? super T
表示类型的下界是T
,可以是T
或T
的父类。- 常用于写入操作,因为可以安全地向集合中添加
T
或其子类。
示例:
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
List<Number> numList = new ArrayList<>();
addNumbers(numList);
System.out.println(numList); // 输出 [1, 2, 3]
扩展知识点:
下界通配符的设计是为了让泛型支持逆变(Contravariance)。比如List<Number>
和List<Object>
都可以被认为是List<? super Integer>
,即可以安全地写入Integer
。
3. 什么时候使用限定通配符?
- 上界通配符
? extends T
: 当你只需要读取数据时使用。例如计算集合中的总和。 - 下界通配符
? super T
: 当你只需要写入数据时使用。例如向集合中添加元素。 - 非限定通配符
?
: 当你只需要操作类型未知的集合,且不涉及具体的读写类型时使用。
4. 通配符和泛型方法的对比
使用通配符的泛型方法:
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
使用泛型参数的泛型方法:
public <T> void printList(List<T> list) {
for (T item : list) {
System.out.println(item);
}
}
区别:
- 通配符关注的是“类型范围”(上界或下界)。
- 泛型方法关注的是“参数类型的一致性”。
总结
- 非限定通配符
?
适合类型未知的情况。 - 上界通配符
? extends T
适合只读场景,支持协变。 - 下界通配符
? super T
适合只写场景,支持逆变。 - 通配符让泛型在灵活性和类型安全性之间达成平衡。