在Java中,泛型(Generics)是提高代码复用性和类型安全的重要特性。然而,泛型的严格类型约束有时会限制代码的灵活性。为了解决这个问题,Java引入了泛型通配符(Wildcards),它允许我们编写更通用的代码,同时保持类型安全。本文将深入探讨泛型通配符的概念、用法以及实际应用场景。
1. 泛型通配符是什么?
泛型通配符用?
表示,它代表一种未知的类型。通配符通常用于以下场景:
- 处理泛型集合时,允许接受多种类型的参数。
- 在方法参数或返回值中,提供更灵活的类型支持。
通配符可以分为以下三种:
- 无界通配符(Unbounded Wildcard) :
<?>
- 上界通配符(Upper Bounded Wildcard) :
<? extends T>
- 下界通配符(Lower Bounded Wildcard) :
<? super T>
2. 无界通配符(<?>)
无界通配符表示任意类型。它通常用于以下场景:
- 当你只关心泛型集合的操作(如遍历),而不关心具体类型时。
2.1 示例:遍历任意类型的集合
java
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("A", "B", "C");
printList(intList); // 输出:1 2 3
printList(strList); // 输出:A B C
}
在这个例子中,printList
方法可以接受任何类型的List
,因为<?>
表示未知类型。
3. 上界通配符(<? extends T>)
上界通配符表示"某种类型或其子类型"。它通常用于以下场景:
- 当你需要从泛型集合中读取数据,但不允许写入数据时。
3.1 示例:处理数字类型的集合
java
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
System.out.println(sumOfList(intList)); // 输出:6.0
System.out.println(sumOfList(doubleList)); // 输出:6.6
}
在这个例子中,sumOfList
方法可以接受Number
或其子类型(如Integer
、Double
)的集合。
注意事项:
- 使用
<? extends T>
时,只能从集合中读取数据,不能写入数据(除了null
),因为具体类型未知。
4. 下界通配符(<? super T>)
下界通配符表示"某种类型或其父类型"。它通常用于以下场景:
- 当你需要向泛型集合中写入数据,但不关心读取数据的类型时。
4.1 示例:向集合中添加元素
java
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
public static void main(String[] args) {
List<Number> numList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
addNumbers(numList); // 添加Integer到Number集合
addNumbers(objList); // 添加Integer到Object集合
System.out.println(numList); // 输出:[1, 2, 3, 4, 5]
System.out.println(objList); // 输出:[1, 2, 3, 4, 5]
}
在这个例子中,addNumbers
方法可以向Integer
或其父类型(如Number
、Object
)的集合中添加元素。
注意事项:
- 使用
<? super T>
时,可以向集合中写入数据,但读取数据时只能以Object
类型接收。
5. PECS原则
为了更清晰地理解上界和下界通配符的使用场景,我们可以参考PECS原则:
- Producer Extends :如果泛型集合是生产者(提供数据),使用
<? extends T>
。 - Consumer Super :如果泛型集合是消费者(接收数据),使用
<? super T>
。
5.1 示例:结合PECS原则
java
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}
public static void main(String[] args) {
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();
copy(src, dest); // 将Integer集合复制到Number集合
System.out.println(dest); // 输出:[1, 2, 3]
}
在这个例子中,src
是生产者(提供数据),因此使用<? extends T>
;dest
是消费者(接收数据),因此使用<? super T>
。
6. 实际应用场景
6.1 集合工具类
Java标准库中的Collections
工具类大量使用了泛型通配符。例如:
java
public static <T> void sort(List<T> list, Comparator<? super T> c) {
// 排序逻辑
}
这里使用<? super T>
是为了允许传入T
或其父类型的比较器。
6.2 泛型方法
在编写泛型方法时,通配符可以提高方法的灵活性。例如:
java
public static <T> void printFirst(List<? extends T> list) {
if (!list.isEmpty()) {
System.out.println(list.get(0));
}
}
7. 总结
泛型通配符是Java泛型中一个强大的工具,它通过<?>
、<? extends T>
和<? super T>
提供了灵活的类型支持。理解并掌握通配符的用法,可以帮助你编写更通用、更安全的代码。记住PECS原则,合理选择上界和下界通配符,可以让你的代码更加优雅和高效。