当前位置: 首页 > news >正文

Java 泛型通配符实战:? extends 与 ? super 到底怎么选(PECS 原则)

Java 泛型通配符实战:? extends 与 ? super 到底怎么选(PECS 原则)

写 Java 的人几乎都被List<? extends Number>List<? super Integer>绕晕过。明明都是通配符,一个只能读不能写,一个能写不能安全读,IDE 报错还看不懂。这篇不背概念,直接从「你写的方法怎么才能同时收List<Integer>List<Double>」这个真实需求出发,把 PECS 讲成肌肉记忆。

先看没有通配符时的死路

假设你要写一个把 List 里的数字求和的方法:

staticdoublesum(List<Number>list){doubles=0;for(Numbern:list)s+=n.doubleValue();returns;}

看着没问题,但你调用时会发现:

List<Integer>ints=List.of(1,2,3);sum(ints);// 编译错误!List<Integer> 不是 List<Number>

很多人这里第一反应是「Integer 是 Number 的子类,List 当然是 List」——。Java 泛型是不变的(invariant):List<Integer>List<Number>没有继承关系。如果允许它们互相赋值,你就能往List<Integer>里塞一个Double,类型安全就崩了。

上界通配符 ? extends:生产者,只读

要让sum收下任意「元素是 Number 或其子类」的 List,用? extends Number:

staticdoublesum(List<?extendsNumber>list){doubles=0;for(Numbern:list)s+=n.doubleValue();// 读出来当 Number 用,安全returns;}List<Integer>ints=List.of(1,2,3);List<Double>doubles=List.of(1.5,2.5);sum(ints);// 编译通过sum(doubles);// 编译通过

? extends Number的含义是:「某个具体但未知的、是 Number 子类型的类型」。因为编译器不知道确切类型,所以:

staticvoiddemo(List<?extendsNumber>list){Numbern=list.get(0);// 读:一定是 Number,允许list.add(1);// 写:编译错误!list.add(null);// 只有 null 例外}

为什么不能写?因为 list 实际可能是List<Double>,你往里 add 一个Integer就破坏了它。编译器无法确认你要加的东西是不是「那个未知子类型」,索性一律禁止。所以? extends的容器只能当数据来源(生产者 Producer),从里面往外拿。

下界通配符 ? super:消费者,可写

反过来,如果你要写一个「往 List 里塞 Integer」的方法,希望它能接受List<Integer>List<Number>List<Object>,用? super Integer:

staticvoidaddNumbers(List<?superInteger>list){for(inti=1;i<=3;i++){list.add(i);// 写:一定能装下 Integer,安全}}List<Number>nums=newArrayList<>();List<Object>objs=newArrayList<>();addNumbers(nums);// 通过addNumbers(objs);// 通过

? super Integer是「某个未知的、是 Integer 父类型的类型」。因为无论那个未知父类型是谁,Integer都能放进去,所以写是安全的。但读呢?

staticvoiddemo(List<?superInteger>list){list.add(42);// 写:允许Integerx=list.get(0);// 读:编译错误!Objecto=list.get(0);// 只能当 Object 读}

读出来的东西编译器只知道「是 Integer 的某个父类」,最保守就是Object,拿不到具体类型。所以? super的容器当数据接收方(消费者 Consumer),只往里塞。

PECS:一句话记死

Effective Java 里的口诀PECS = Producer-Extends, Consumer-Super:

  • 参数是生产数据给你用的(你从里面读)→ 用? extends
  • 参数是消费你给的数据的(你往里面写)→ 用? super

经典案例是Collections.copy,一个读一个写,两个通配符一起上:

publicstatic<T>voidcopy(List<?superT>dest,List<?extendsT>src){for(inti=0;i<src.size();i++){dest.set(i,src.get(i));// 从 src 读(生产者 extends),往 dest 写(消费者 super)}}List<Object>dest=newArrayList<>(List.of(0,0,0));List<Integer>src=List.of(1,2,3);copy(dest,src);// Integer 能读、能塞进 Object 列表

src是数据来源用extends,dest是数据去处用super,完美对应。

一个真实踩坑:自己的接口忘了加通配符

假设你写了个事件处理器接口:

interfaceHandler<T>{voidhandle(Tevent);}// 想注册:一个能处理 Object 的 handler 应该也能处理 String 事件staticvoidregister(Handler<String>h){/* ... */}Handler<Object>universal=e->System.out.println(e);register(universal);// 编译错误!Handler<Object> 不是 Handler<String>

universal明明能处理任何 Object(包括 String),却传不进去。因为handle消费event 的(消费者),按 PECS 参数该用super:

staticvoidregister(Handler<?superString>h){h.handle("hello");}register(universal);// 现在通过了

判断依据永远是看方法内部对这个泛型参数是读还是写:handle往里塞(消费)String,就用super

小结

  • Java 泛型不变:List<Integer>不是List<Number>,别指望自动向上转。
  • ? extends X:生产者,只能读(读出来是 X),不能写(null 除外)。数据从它流出。
  • ? super X:消费者,能写(写 X 安全),读只能当 Object。数据往它流入。
  • PECS:你读它就extends,你写它就super;既读又写(如copy)两个一起用。
  • 设计自己的泛型方法时,盯住「方法体里对这个参数是 get 还是 add」来决定用哪个。

一句话记忆点:Producer-Extends,Consumer-Super——从哪读用 extends,往哪写用 super

http://www.jsqmd.com/news/1261863/

相关文章:

  • JUnit 5入门指南:从HelloWorld测试到DevOps集成实践
  • YOLOv8在塑料焊缝缺陷检测中的实践与优化
  • 如何免费突破百度网盘限速:BaiduPCS-Web完整指南
  • Kubernetes 滚动更新与回滚实战:maxSurge、maxUnavailable 与卡住的排查
  • 专科生必看:实测有效的AI工具降AI率指南
  • 2026 年当下,雨花热门的便民服务岗亭企业哪家靠谱,这个岗亭,如何让你的生活效率翻倍? - 企业推荐官【认证官方】
  • 郑州药厂净化工程直销厂家靠谱选型实用指南 - 品牌优推
  • 终极鼠标键盘录制自动化工具:KeymouseGo 完整入门指南
  • 金华黄金回收避坑实录:街坊踩过的坑,替你问了5家老店 - 小城生活闲谈
  • Apollo Save Tool:无需电脑,在PS4上管理游戏存档的终极方案
  • 5步掌握iOS越狱:从新手到专家的完整越狱指南
  • RGB-IR双模态目标检测的输入级融合方法与实践
  • 5分钟快速上手:LibreHardwareMonitor免费开源硬件监控终极指南
  • 腾讯元宝多轮问答怎么按项目归档?DS随心转先免费备份Markdown - 【DS随心转】
  • 仅限HRBP与CTO可见:某独角兽公司封存的AI员工关怀训练集(含2.1万条真实离职对话标注样本+情绪衰减曲线模型)
  • 基于python的超市管理系统设计与实现
  • 零基础搭建线上评选,微信投票防刷设置详细教程 - 微信投票小程序
  • 关于文献【26ACL三篇最佳论文的具体归属?】
  • 博尔塔拉州雾炮机选购指南:如何选到合规高效的降尘设备 - 全域品牌推荐
  • AI配音角色混淆率超15%?资深TTS工程师首次公开3层声学解耦架构与角色ID嵌入式训练范式
  • MCP协议详解:大模型安全访问外部数据的标准化方案
  • 基于ROS与毫米波雷达的嵌入式机器人导航系统实战解析
  • AMD显卡驱动问题解决:安全回退旧版与驱动管理最佳实践
  • WVP-GB28181-Pro:5分钟搭建专业级国标视频监控平台的完整指南
  • 如何安全越狱iOS 26.5:2026年终极指南解锁iPhone隐藏功能
  • 2026湖州闲置黄金变现避坑全攻略:每日核对大盘价选备案门店,合规门店名录一站式参考 - 商业资讯新知
  • 2026年制造业图纸识别与检验计划自动化实务:Infra CONVERT 正版授权 的应用逻辑
  • 基于Python与Unity的VTuber实时动捕系统:从零搭建低成本虚拟形象驱动方案
  • UE5蓝图音频系统全解析:从基础配置到交互式音效设计实战
  • Qwen3大模型金融风控微调实战与优化