只读数据流用 ? extends T,核心是保障读取安全而非放宽类型;判断 Producer 场景唯一看方法体内是否仅执行 get() 等读操作且元素按 T 或其父类处理。

只读数据流用 ? extends T,核心是让方法能接收更具体的子类集合,同时保证取出的数据一定能当 T 安全使用——不是为了“放宽类型”,而是为了“守住读取安全边界”。
什么时候算真正的 Producer 场景
判断依据只有一个:方法体内是否只调用 get()、iterator().next()、stream() 等读取操作,且所有取出的元素都按 T 或其父类 处理(比如赋值给 T 变量、传给 T 参数的方法、调用 T 声明的方法)。
- ✅ 符合:遍历打印姓名、计算平均分、筛选符合条件的子集、转成 JSON 字符串
- ❌ 不符合:往里 add 新元素、做 instanceof 判断后强转为子类、把取出对象再塞进另一个泛型为子类的集合
典型写法与避坑要点
以 List extends Number> 为例:
- 允许:
Number n = list.get(0);、double sum = list.stream().mapToDouble(Number::doubleValue).sum(); - 禁止:
list.add(42);(编译报错)、list.set(0, new BigDecimal("1"));(同样报错) - 唯一例外:
list.add(null);—— 因为null是所有引用类型的合法值 - 常见误用:以为
? extends Number表示“可以装任意数字”,其实它表示“只能从里面安全拿出Number”,底层可能是List<integer></integer>,也可能是List<atomiclong></atomiclong>,编译器不让你写,就是怕你塞错
实战中提升灵活性的组合技巧
单独用 ? extends T 已经能解决大部分只读兼容问题,但配合其他设计可进一步释放能力:
- 与泛型方法结合:写一个
<t> void process(List extends T> src, Consumer<t> handler)</t></t>,调用时可传List<string></string>、List<object></object>(若T=Object),语义清晰又无强制转换 - 在工具类中统一收口:比如日志框架的批量记录接口
void logAll(List extends LogEntry> entries),上游无论是List<errorlog></errorlog>还是List<accesslog></accesslog>都能直接传入,无需新建中间列表 - 避免过度泛化:不要为了“看起来通用”而滥用
? extends Object,它等价于?,失去类型约束,反而削弱编译期检查价值
和下游消费者联动:PECS 成对出现才高效
只读提取常是数据流转的第一步。真正发挥 PECS 优势,是在和 ? super T 配合时:
- 例如通用拷贝方法:
public static <t> void copy(List extends T> src, List super T> dest)</t> - 调用
copy(students, persons)(Student → Person)、copy(integers, numbers)(Integer → Number)都合法 - 上游用
extends安全产出,下游用super安全接收,中间无需类型擦除或临时转换,链路干净、类型安全、性能无损

















