一、<? extends T>是什么,<? super T>又是什么
在Java泛型中,<? extends T> 和 <? super T> 是通配符(Wildcard)的核心语法,用于类型边界约束。它们分别对应PECS法则中的生产者(Producer)和消费者(Consumer)场景,具体含义、用法及限制如下:
1. <? extends T>(上界通配符)
含义
- 表示集合的元素类型是
T或T的任意子类(如T是Number,则可以是Integer、Double等)。 - 用于生产者场景(只读不写,集合是生产者),即从集合中安全读取数据。
用法
- 方法参数:当方法需要读取集合元素时,使用
List<? extends T>接收参数。 - 返回值:当方法返回不确定具体子类的集合时,可用
? extends T声明返回类型(但较少见)。
限制
- 禁止写入非
null元素:因为编译器无法确定具体类型,无法保证添加的元素类型安全。List<? extends Number> numbers = new ArrayList<Integer>(); numbers.add(1); // 编译错误:无法确定具体类型 numbers.add(null); // 允许,因为null是所有类型的子类 - 读取时类型安全:读取的元素可赋值给
T或T的父类(如Object)。
代码示例
// 生产者方法:读取任意Number子类的List
public static void printNumbers(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num); // 安全读取,类型为Number或其子类
}
}
// 调用示例
List<Integer> integers = Arrays.asList(1, 2, 3);
printNumbers(integers); // 编译通过,Integer是Number的子类
2. <? super T>(下界通配符)
含义
- 表示集合的元素类型是
T或T的任意父类(如T是Integer,则可以是Number、Object等)。 - 用于消费者场景(只写不读,要写的类型是确定的),即向集合中安全添加数据。
用法
- 方法参数:当方法需要写入集合元素时,使用
List<? super T>接收参数。 - 返回值:几乎不用于返回类型(因为类型信息丢失)。
限制
- 读取时类型丢失:从集合中读取的元素只能用
Object接收(无法确定具体类型)。List<? super Number> numbers = new ArrayList<Object>(); Object obj = numbers.get(0); // 只能用Object接收 Number num = numbers.get(0); // 编译错误:类型不安全写入时类型安全:可安全添加
T或T的子类元素。
代码示例
// 消费者方法:向任意Number父类的List中添加数据
public static void addNumbers(List<? super Number> numbers) {
numbers.add(1); // 安全添加Integer(Number的子类)
numbers.add(1.1); // 安全添加Double(Number的子类)
}
// 调用示例
List<Object> objects = new ArrayList<>();
addNumbers(objects); // 编译通过,Object是Number的父类
3. 对比总结
| 特性 | ? extends T(上界) | ? super T(下界) |
|---|---|---|
| 适用场景 | 生产者(只读) | 消费者(只写) |
| 类型约束 | 元素为T或其子类 | 元素为T或其父类 |
| 允许操作 | 安全读取(T或父类) | 安全写入(T或子类) |
| 禁止操作 | 写入非null元素(类型不确定) | 读取具体类型(只能用Object) |
| 典型用途 | 方法参数(如Collections.copy源) | 方法参数(如Collections.copy目标) |
4. 实际应用场景
- 集合拷贝:JDK中的
Collections.copy(List<? super T> dest, List<? extends T> src)方法,确保源集合(生产者)可读,目标集合(消费者)可写。 - 自定义工具类:如自定义栈的
popAll(Collection<? super T> dest)方法,将栈元素弹出到任意父类集合中。 - 框架设计:在需要灵活处理子类或父类集合的场景中,避免类型强制转换。
5. 注意事项
- 避免在类定义中使用通配符:类定义应使用具体类型参数(如
class Box<T>),通配符主要用于方法参数。 - 优先使用通配符而非具体类型:在方法参数中,使用
List<? extends T>比List<T>更灵活(可接受子类集合)。 - 类型擦除的影响:运行时通配符会被擦除为
Object,但编译期的类型检查仍能保证安全。
通过理解? extends T和? super T的差异及约束,可以编写更灵活、安全的泛型代码,避免运行时类型错误。
二、PECS法则
PECS法则(Producer Extends, Consumer Super)是Java泛型中用于确定通配符边界的核心原则,其核心逻辑是通过类型安全约束优化生产者(只读)和消费者(只写)场景的代码设计。
以下从原理、代码示例、应用场景三个层面进行详细说明(略显重复,加深记忆~~~):
PECS法则原理
- Producer Extends(生产者使用
? extends T)- 适用场景:当集合作为数据生产者(只读不写),需从集合中安全读取数据时。
- 类型约束:通配符上限为
T,即集合元素类型为T或其子类。 - 限制:无法向集合添加非
null元素(编译器无法确定具体类型)。 - 优势:确保读取的数据一定是
T或其子类,避免类型污染。
- Consumer Super(消费者使用
? super T)- 适用场景:当集合作为数据消费者(只写不读),需向集合中安全添加数据时。
- 类型约束:通配符下限为
T,即集合元素类型为T或其父类。 - 限制:从集合中读取的数据只能用
Object接收(类型信息丢失)。 - 优势:确保添加的数据一定是
T或其子类,避免类型错误。
代码示例
生产者场景(? extends T)
import java.util.Arrays;
import java.util.List;
public class ProducerExample {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
// 生产者方法:接收任何Number或其子类的List
processNumbers(integers); // 编译通过,Integer是Number的子类
processNumbers(doubles); // 编译通过,Double是Number的子类
}
// 生产者方法:只读取数据,不写入
public static void processNumbers(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num); // 安全读取,类型为Number或其子类
}
// numbers.add(1); // 编译错误,无法添加非null元素
}
}
说明:
List<? extends Number>可接受List<Integer>、List<Double>等,但无法调用add()方法(编译器无法确定具体类型)。- 读取时可用
Number或其父类(如Object)接收,确保类型安全。
消费者场景(? super T)
import java.util.ArrayList;
import java.util.List;
public class ConsumerExample {
public static void main(String[] args) {
List<Object> objects = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
// 消费者方法:接收任何Number或其父类的List
addNumbers(objects); // 编译通过,Object是Number的父类
addNumbers(numbers); // 编译通过,Number是自身父类
}
// 消费者方法:只写入数据,不读取
public static void addNumbers(List<? super Number> numbers) {
numbers.add(1); // 安全添加Integer(Number的子类)
numbers.add(1.1); // 安全添加Double(Number的子类)
// Number num = numbers.get(0); // 编译错误,只能用Object接收
Object obj = numbers.get(0); // 只能用Object接收,类型信息丢失
}
}
说明:
List<? super Number>可接受List<Object>、List<Number>等,但读取时只能用Object接收(编译器无法确定具体类型)。- 添加时可用
Number或其子类(如Integer、Double),确保类型兼容。
应用场景
- 集合拷贝(
Collections.copy)
JDK中的Collections.copy方法同时使用? extends T和? super T:public static <T> void copy(List<? super T> dest, List<? extends T> src) { // dest是消费者(只写),src是生产者(只读) }src作为生产者,使用? extends T确保读取的数据是T或其子类。dest作为消费者,使用? super T确保写入的数据是T或其子类。
- 栈的批量操作
自定义栈的pushAll和popAll方法:public class Stack<E> { // 消费者方法:接收任何E或其子类的Iterable public void pushAll(Iterable<? extends E> src) { for (E e : src) { push(e); } } // 生产者方法:将元素弹出到任何E或其父类的Collection public void popAll(Collection<? super E> dest) { while (!isEmpty()) { dest.add(pop()); } } }pushAll的src是生产者,使用? extends E确保读取的数据是E或其子类。popAll的dest是消费者,使用? super E确保写入的数据是E或其子类。
总结
| 场景 | 通配符 | 类型约束 | 允许操作 | 典型用途 |
|---|---|---|---|---|
| 生产者(只读) | ? extends T | 元素类型为T或其子类 | 安全读取,禁止写入 | 方法参数、集合拷贝源 |
| 消费者(只写) | ? super T | 元素类型为T或其父类 | 安全写入,限制读取 | 方法参数、集合拷贝目标 |
PECS法则的本质是通过类型边界约束,在编译期确保生产者-消费者模式的类型安全,避免运行时ClassCastException。

1444

被折叠的 条评论
为什么被折叠?



