目录1. 面试真题引入2. 底层原理解构类型参数与通配符源码透视2.1 T/E/K/V固定类型变量泛型类的「类型模板」2.2 通配符外挂的灵活占位符2.3 上下界对决? extends T 生产者 vs ? super T 消费者1. 上界通配符 ? extends T 生产者只读2. 下界通配符 ? super T 消费者只写2.4 PECS 设计思想读写分离的协变逆变模型2.5 泛型契约类型安全三条数学约束协变约束List 是 List 的子集但父子泛型不可直接赋值无泛型协变写入隔离上界通配符禁止 add 任意实体避免类型污染读取隔离下界通配符读取丢失子类类型只能向上转为 Object。 底层原理类型擦除编译后泛型全部转为 Object通配符只是编译期语法校验。3. 纯手工实战电商商品库存管理系统3.1 商品分层模型3.2 测试数据3.3 生产者场景? extends T 批量读取商品3.4 消费者场景? super T 批量新增商品3.5 链式组合泛型 Comparator 多级筛选排序4. 避坑指南TreeSet/TreeMap 泛型隐形 Bug4.1 默认排序泛型边界不匹配4.2 上下界读写混淆出现编译红线4.3 T extends Comparable 自限定泛型陷阱5. 面试连环炮 Mock Interview6. 类比小结1. 面试真题引入Java 后端面试泛型通配符是必考题从一面基础问到二面架构设计。面试官标准提问流程先问「T 和有什么区别」你答完类型参数与未知通配符立刻追问「extends和 super 分别什么时候用PECS 原则是什么」接着落地集合场景「TreeSet 构造器为什么是Comparator? super E」最后抛出业务场景「写一个通用方法既能读取手机、电脑子类商品又能批量新增商品泛型怎么定义」三层问题连环追问只要混淆读写边界、分不清生产者消费者直接扣分。泛型不是简单语法糖是一套类型安全的契约设计吃透底层规则才能避开线上隐藏 bug。2. 底层原理解构类型参数与通配符源码透视2.1 T/E/K/V固定类型变量泛型类的「类型模板」T、E、K、V 属于声明式类型参数定义泛型类、接口、方法时提前声明全程统一使用同一个确定类型。 命名约定T通用任意类型TypeE集合元素ElementList/Set 专用K/VMap 键值Key/Value// 定义时声明T整个类中T是固定单一类型 public class GoodsBoxT { private T goods; public void set(T goods) { this.goods goods; } public T get() { return goods; } }核心特点编译时锁定唯一类型写入读取完全类型安全一个泛型类只能绑定一种 T不能同时兼容父类、多个子类可加边界约束T extends Goods限定只能是商品及其子类。本质给类预留「类型空位」实例化时填充唯一类型如同定制包装盒只能装一类物品。2.2 通配符外挂的灵活占位符?是使用阶段的未知通配符仅用于方法参数、变量接收不能在定义泛型类时声明代表「不确定的某一类」。// 通配符仅用在接收参数无法固定类型 public static void printAll(List? list) { for (Object obj : list) { System.out.println(obj); } // list.add(xxx); 编译报错无法写入 }和 T 核心差异对比维度T类型参数?通配符声明位置类 / 方法定义处T仅变量、方法入参类型确定性全程固定单一类型未知仅匹配范围读写能力可读可写无界通配符只能读 Object禁止写入使用场景自定义泛型工具类通用工具方法兼容多子类集合2.3 上下界对决? extends T生产者 vs? super T消费者1. 上界通配符? extends T生产者只读代表T 本身 T 所有子类只向外输出数据生产者 Producer。 规则只能安全读取为 T禁止调用 add 写入。// 只能读取商品不能新增 public static void showGoods(List? extends Goods list) { Goods g list.get(0); // 安全读取父类 // list.add(new Phone()); 编译报错无法写入 }比喻仓库只负责出货只能拿商品不能往里放新品。2. 下界通配符? super T消费者只写代表T 本身 T 所有父类只接收外部数据消费者 Consumer。 规则可以安全写入 T 及其子类读取只能拿到 Object无法强转父类。// 可以写入手机子类商品读取只能Object public static void addPhone(List? super Phone list) { list.add(new Phone()); // 合法写入子类 Object obj list.get(0); // 读取只能Object // Phone p list.get(0); 编译报错 }比喻快递收件箱只能往里放手机拿出来分不清是什么物品。2.4 PECS 设计思想读写分离的协变逆变模型PECS 全称Producer ExtendsConsumer Super泛型通配符核心设计准则如果方法只读取集合、不新增元素生产者→ 使用? extends T如果方法只新增元素、不依赖读取后的类型消费者→ 使用? super T既要读又要写不能使用通配符改用固定类型参数T。JDK 源码经典佐证TreeSet 构造器public TreeSet(Comparator? super E comparator)Comparator 只负责比较元素接收 E 做参数属于消费者因此下界? super E兼容 E、E 父类比较器。2.5 泛型契约类型安全三条数学约束使用带边界的泛型必须遵守编译期隐式契约违反直接报类型不匹配协变约束ListPhone是List? extends Goods的子集但父子泛型不可直接赋值无泛型协变写入隔离上界通配符禁止 add 任意实体避免类型污染读取隔离下界通配符读取丢失子类类型只能向上转为 Object。 底层原理类型擦除编译后泛型全部转为 Object通配符只是编译期语法校验。3. 纯手工实战电商商品库存管理系统3.1 商品分层模型父类商品手机、电脑为子类模拟电商分层品类// 顶层父类 public class Goods implements ComparableGoods { private String name; private Double price; private Integer stock; public Goods(String name, Double price, Integer stock) { this.name name; this.price price; this.stock stock; } // 自然排序库存升序 Override public int compareTo(Goods o) { return Integer.compare(this.stock, o.stock); } // getter toString省略 public String getName() { return name; } public Double getPrice() { return price; } public Integer getStock() { return stock; } Override public String toString() { return String.format(%-6s 价格:%.2f 库存:%d, name, price, stock); } } // 子类1手机 public class Phone extends Goods { private String cpu; public Phone(String name, Double price, Integer stock, String cpu) { super(name, price, stock); this.cpu cpu; } } // 子类2电脑 public class Computer extends Goods { private String gpu; public Computer(String name, Double price, Integer stock, String gpu) { super(name, price, stock); this.gpu gpu; } }3.2 测试数据ListPhone phoneList Arrays.asList( new Phone(iPhone16, 5999.0, 120, A18), new Phone(Mate70, 4999.0, 260, 麒麟9010) ); ListComputer pcList Arrays.asList( new Computer(拯救者, 7999.0, 80, RTX4060), new Computer(MacBook, 8999.0, 50, M3) );3.3 生产者场景? extends T批量读取商品仅读取打印所有商品不修改集合符合 Producer 场景使用上界通配符// 生产者只读取使用? extends Goods public static void showAllGoods(List? extends Goods goodsList) { System.out.println( 商品列表 ); goodsList.forEach(System.out::println); } // 调用手机、电脑子类集合均可传入 showAllGoods(phoneList); showAllGoods(pcList);输出 商品列表 iPhone16 价格:5999.00 库存:120 Mate70 价格:4999.00 库存:260 商品列表 拯救者 价格:7999.00 库存:80 MacBook 价格:8999.00 库存:50如果强行goodsList.add()IDE 直接标红编译失败从语法杜绝类型错误。3.4 消费者场景? super T批量新增商品统一往库存容器新增手机只写入不依赖读取类型Consumer 场景用下界通配符// 消费者只新增手机使用? super Phone public static void batchAddPhone(List? super Phone box) { box.add(new Phone(小米14, 3999.0, 300, 骁龙8Gen3)); box.add(new Phone(一加12, 4299.0, 210, 骁龙8Gen3)); } // 父类集合、Phone集合都能接收 ListGoods allGoods new ArrayList(); batchAddPhone(allGoods); batchAddPhone(new ArrayListPhone());3.5 链式组合泛型 Comparator 多级筛选排序结合前文 Comparator 链式排序用泛型工具筛选高价商品并按价格降序// 固定类型参数既能读又能写 public static T extends Goods ListT filterHighPrice(ListT list) { // 链式比较价格降序库存升序 ComparatorT priceSort Comparator .comparing(T::getPrice, Comparator.reverseOrder()) .thenComparing(T::getStock); return list.stream() .filter(g - g.getPrice() 5000) .sorted(priceSort) .collect(Collectors.toList()); } // 调用 ListPhone highPhone filterHighPrice(phoneList); highPhone.forEach(System.out::println);4. 避坑指南TreeSet/TreeMap 泛型隐形 Bug4.1 默认排序泛型边界不匹配TreeSet 默认使用元素Comparable若泛型边界写死TreeSetPhone只能存 Phone无法存入 Computer 如果需要统一存放所有商品定义为TreeSetGoods或构造器传入Comparator? super Goods。// 正确下界兼容所有商品比较器 TreeSetGoods goodsSet new TreeSet(Comparator.comparing(Goods::getPrice));4.2 上下界读写混淆出现编译红线高频错误拿到List? extends Goods后调用 add或用List? super Phone读取后强转 Phone。 解决方案严格遵循 PECS读写分开既要读又要写就不用通配符使用T固定类型。4.3T extends ComparableT自限定泛型陷阱自定义泛型容器时很多人写错边界// 错误 public class BoxT extends Comparable {} // 正确 自限定保证T自身具备比较能力 public class BoxT extends ComparableT {}错误写法会丢失泛型参数TreeSet 排序时触发未经检查的转换警告极端情况排序错乱。5. 面试连环炮 Mock Interview面试官先说 T 和的核心区别 求职者T 是定义泛型类 / 方法时声明的固定类型参数全程类型统一可读可写? 是仅在使用时的通配符代表未知类型分上下界只读或只写无法固定类型。面试官那extends和 super 分别对应什么场景PECS 怎么理解 求职者PECS 即生产者用 extends消费者用 super。? extends T上界集合只做读取输出兼容 T 所有子类禁止写入? super T下界集合只接收写入数据兼容 T 所有父类读取只能 Object读写都要操作集合不能使用通配符改用T类型参数。面试官为什么 TreeSet 构造器的 Comparator 是Comparator? super E而不是? extends E 求职者Comparator 的 compare 方法接收两个 E 参数只负责接收元素做对比属于消费者逻辑。使用下界? super E可以传入 E、E 父类的比较器扩展性更强如果用 extends 只能传子类比较器父类比较器无法传入限制过大。面试官如果我需要一个方法既能存入手机、电脑又能读取后拿到完整商品信息泛型该怎么写 求职者不能用通配符使用带边界的固定类型参数T extends Goods方法入参 ListT此时既能 add 子类商品读取后也能直接转为 Goods / 子类兼顾读写。6. 类比小结把泛型体系比作快递物流系统一秒分清所有概念T 固定类型参数 专属快递箱只能装一类货物可放可取? 无界通配符 临时分拣通道只能看不能放? extends T上界生产者 出货分拣台只能取出商品不能投放? super T下界消费者 统一收件仓所有子类都能放取出分不清品类PECS 准则 物流分工规则出货用 extends、收件用 superComparator? super E 通用验货员既能验手机也能验电脑兼容所有商品父类规则。
Java 泛型怎么灵活读写?一篇讲透 T、?、extends 与 super 的设计智慧
目录1. 面试真题引入2. 底层原理解构类型参数与通配符源码透视2.1 T/E/K/V固定类型变量泛型类的「类型模板」2.2 通配符外挂的灵活占位符2.3 上下界对决? extends T 生产者 vs ? super T 消费者1. 上界通配符 ? extends T 生产者只读2. 下界通配符 ? super T 消费者只写2.4 PECS 设计思想读写分离的协变逆变模型2.5 泛型契约类型安全三条数学约束协变约束List 是 List 的子集但父子泛型不可直接赋值无泛型协变写入隔离上界通配符禁止 add 任意实体避免类型污染读取隔离下界通配符读取丢失子类类型只能向上转为 Object。 底层原理类型擦除编译后泛型全部转为 Object通配符只是编译期语法校验。3. 纯手工实战电商商品库存管理系统3.1 商品分层模型3.2 测试数据3.3 生产者场景? extends T 批量读取商品3.4 消费者场景? super T 批量新增商品3.5 链式组合泛型 Comparator 多级筛选排序4. 避坑指南TreeSet/TreeMap 泛型隐形 Bug4.1 默认排序泛型边界不匹配4.2 上下界读写混淆出现编译红线4.3 T extends Comparable 自限定泛型陷阱5. 面试连环炮 Mock Interview6. 类比小结1. 面试真题引入Java 后端面试泛型通配符是必考题从一面基础问到二面架构设计。面试官标准提问流程先问「T 和有什么区别」你答完类型参数与未知通配符立刻追问「extends和 super 分别什么时候用PECS 原则是什么」接着落地集合场景「TreeSet 构造器为什么是Comparator? super E」最后抛出业务场景「写一个通用方法既能读取手机、电脑子类商品又能批量新增商品泛型怎么定义」三层问题连环追问只要混淆读写边界、分不清生产者消费者直接扣分。泛型不是简单语法糖是一套类型安全的契约设计吃透底层规则才能避开线上隐藏 bug。2. 底层原理解构类型参数与通配符源码透视2.1 T/E/K/V固定类型变量泛型类的「类型模板」T、E、K、V 属于声明式类型参数定义泛型类、接口、方法时提前声明全程统一使用同一个确定类型。 命名约定T通用任意类型TypeE集合元素ElementList/Set 专用K/VMap 键值Key/Value// 定义时声明T整个类中T是固定单一类型 public class GoodsBoxT { private T goods; public void set(T goods) { this.goods goods; } public T get() { return goods; } }核心特点编译时锁定唯一类型写入读取完全类型安全一个泛型类只能绑定一种 T不能同时兼容父类、多个子类可加边界约束T extends Goods限定只能是商品及其子类。本质给类预留「类型空位」实例化时填充唯一类型如同定制包装盒只能装一类物品。2.2 通配符外挂的灵活占位符?是使用阶段的未知通配符仅用于方法参数、变量接收不能在定义泛型类时声明代表「不确定的某一类」。// 通配符仅用在接收参数无法固定类型 public static void printAll(List? list) { for (Object obj : list) { System.out.println(obj); } // list.add(xxx); 编译报错无法写入 }和 T 核心差异对比维度T类型参数?通配符声明位置类 / 方法定义处T仅变量、方法入参类型确定性全程固定单一类型未知仅匹配范围读写能力可读可写无界通配符只能读 Object禁止写入使用场景自定义泛型工具类通用工具方法兼容多子类集合2.3 上下界对决? extends T生产者 vs? super T消费者1. 上界通配符? extends T生产者只读代表T 本身 T 所有子类只向外输出数据生产者 Producer。 规则只能安全读取为 T禁止调用 add 写入。// 只能读取商品不能新增 public static void showGoods(List? extends Goods list) { Goods g list.get(0); // 安全读取父类 // list.add(new Phone()); 编译报错无法写入 }比喻仓库只负责出货只能拿商品不能往里放新品。2. 下界通配符? super T消费者只写代表T 本身 T 所有父类只接收外部数据消费者 Consumer。 规则可以安全写入 T 及其子类读取只能拿到 Object无法强转父类。// 可以写入手机子类商品读取只能Object public static void addPhone(List? super Phone list) { list.add(new Phone()); // 合法写入子类 Object obj list.get(0); // 读取只能Object // Phone p list.get(0); 编译报错 }比喻快递收件箱只能往里放手机拿出来分不清是什么物品。2.4 PECS 设计思想读写分离的协变逆变模型PECS 全称Producer ExtendsConsumer Super泛型通配符核心设计准则如果方法只读取集合、不新增元素生产者→ 使用? extends T如果方法只新增元素、不依赖读取后的类型消费者→ 使用? super T既要读又要写不能使用通配符改用固定类型参数T。JDK 源码经典佐证TreeSet 构造器public TreeSet(Comparator? super E comparator)Comparator 只负责比较元素接收 E 做参数属于消费者因此下界? super E兼容 E、E 父类比较器。2.5 泛型契约类型安全三条数学约束使用带边界的泛型必须遵守编译期隐式契约违反直接报类型不匹配协变约束ListPhone是List? extends Goods的子集但父子泛型不可直接赋值无泛型协变写入隔离上界通配符禁止 add 任意实体避免类型污染读取隔离下界通配符读取丢失子类类型只能向上转为 Object。 底层原理类型擦除编译后泛型全部转为 Object通配符只是编译期语法校验。3. 纯手工实战电商商品库存管理系统3.1 商品分层模型父类商品手机、电脑为子类模拟电商分层品类// 顶层父类 public class Goods implements ComparableGoods { private String name; private Double price; private Integer stock; public Goods(String name, Double price, Integer stock) { this.name name; this.price price; this.stock stock; } // 自然排序库存升序 Override public int compareTo(Goods o) { return Integer.compare(this.stock, o.stock); } // getter toString省略 public String getName() { return name; } public Double getPrice() { return price; } public Integer getStock() { return stock; } Override public String toString() { return String.format(%-6s 价格:%.2f 库存:%d, name, price, stock); } } // 子类1手机 public class Phone extends Goods { private String cpu; public Phone(String name, Double price, Integer stock, String cpu) { super(name, price, stock); this.cpu cpu; } } // 子类2电脑 public class Computer extends Goods { private String gpu; public Computer(String name, Double price, Integer stock, String gpu) { super(name, price, stock); this.gpu gpu; } }3.2 测试数据ListPhone phoneList Arrays.asList( new Phone(iPhone16, 5999.0, 120, A18), new Phone(Mate70, 4999.0, 260, 麒麟9010) ); ListComputer pcList Arrays.asList( new Computer(拯救者, 7999.0, 80, RTX4060), new Computer(MacBook, 8999.0, 50, M3) );3.3 生产者场景? extends T批量读取商品仅读取打印所有商品不修改集合符合 Producer 场景使用上界通配符// 生产者只读取使用? extends Goods public static void showAllGoods(List? extends Goods goodsList) { System.out.println( 商品列表 ); goodsList.forEach(System.out::println); } // 调用手机、电脑子类集合均可传入 showAllGoods(phoneList); showAllGoods(pcList);输出 商品列表 iPhone16 价格:5999.00 库存:120 Mate70 价格:4999.00 库存:260 商品列表 拯救者 价格:7999.00 库存:80 MacBook 价格:8999.00 库存:50如果强行goodsList.add()IDE 直接标红编译失败从语法杜绝类型错误。3.4 消费者场景? super T批量新增商品统一往库存容器新增手机只写入不依赖读取类型Consumer 场景用下界通配符// 消费者只新增手机使用? super Phone public static void batchAddPhone(List? super Phone box) { box.add(new Phone(小米14, 3999.0, 300, 骁龙8Gen3)); box.add(new Phone(一加12, 4299.0, 210, 骁龙8Gen3)); } // 父类集合、Phone集合都能接收 ListGoods allGoods new ArrayList(); batchAddPhone(allGoods); batchAddPhone(new ArrayListPhone());3.5 链式组合泛型 Comparator 多级筛选排序结合前文 Comparator 链式排序用泛型工具筛选高价商品并按价格降序// 固定类型参数既能读又能写 public static T extends Goods ListT filterHighPrice(ListT list) { // 链式比较价格降序库存升序 ComparatorT priceSort Comparator .comparing(T::getPrice, Comparator.reverseOrder()) .thenComparing(T::getStock); return list.stream() .filter(g - g.getPrice() 5000) .sorted(priceSort) .collect(Collectors.toList()); } // 调用 ListPhone highPhone filterHighPrice(phoneList); highPhone.forEach(System.out::println);4. 避坑指南TreeSet/TreeMap 泛型隐形 Bug4.1 默认排序泛型边界不匹配TreeSet 默认使用元素Comparable若泛型边界写死TreeSetPhone只能存 Phone无法存入 Computer 如果需要统一存放所有商品定义为TreeSetGoods或构造器传入Comparator? super Goods。// 正确下界兼容所有商品比较器 TreeSetGoods goodsSet new TreeSet(Comparator.comparing(Goods::getPrice));4.2 上下界读写混淆出现编译红线高频错误拿到List? extends Goods后调用 add或用List? super Phone读取后强转 Phone。 解决方案严格遵循 PECS读写分开既要读又要写就不用通配符使用T固定类型。4.3T extends ComparableT自限定泛型陷阱自定义泛型容器时很多人写错边界// 错误 public class BoxT extends Comparable {} // 正确 自限定保证T自身具备比较能力 public class BoxT extends ComparableT {}错误写法会丢失泛型参数TreeSet 排序时触发未经检查的转换警告极端情况排序错乱。5. 面试连环炮 Mock Interview面试官先说 T 和的核心区别 求职者T 是定义泛型类 / 方法时声明的固定类型参数全程类型统一可读可写? 是仅在使用时的通配符代表未知类型分上下界只读或只写无法固定类型。面试官那extends和 super 分别对应什么场景PECS 怎么理解 求职者PECS 即生产者用 extends消费者用 super。? extends T上界集合只做读取输出兼容 T 所有子类禁止写入? super T下界集合只接收写入数据兼容 T 所有父类读取只能 Object读写都要操作集合不能使用通配符改用T类型参数。面试官为什么 TreeSet 构造器的 Comparator 是Comparator? super E而不是? extends E 求职者Comparator 的 compare 方法接收两个 E 参数只负责接收元素做对比属于消费者逻辑。使用下界? super E可以传入 E、E 父类的比较器扩展性更强如果用 extends 只能传子类比较器父类比较器无法传入限制过大。面试官如果我需要一个方法既能存入手机、电脑又能读取后拿到完整商品信息泛型该怎么写 求职者不能用通配符使用带边界的固定类型参数T extends Goods方法入参 ListT此时既能 add 子类商品读取后也能直接转为 Goods / 子类兼顾读写。6. 类比小结把泛型体系比作快递物流系统一秒分清所有概念T 固定类型参数 专属快递箱只能装一类货物可放可取? 无界通配符 临时分拣通道只能看不能放? extends T上界生产者 出货分拣台只能取出商品不能投放? super T下界消费者 统一收件仓所有子类都能放取出分不清品类PECS 准则 物流分工规则出货用 extends、收件用 superComparator? super E 通用验货员既能验手机也能验电脑兼容所有商品父类规则。