
学习背景前段时间师姐分享CRTP(自引用泛型约束)时发现对泛型中的T和?概念有点模糊赶紧找来资料学习一下总结如下一、基础前置类class Animal {} class Dog extends Animal {} class Cat extends Animal {}二、核心概念定义2.1 类型参数命名泛型T定义在类/方法头部提前声明有名字的类型代号T/E/K/R可添加 extends 边界约束整个类/方法作用域内可重复复用该类型实现入参、返回值、局部变量统一绑定同一类型。设计初衷 解决的核心问题Java 早期无泛型时集合统一为原始类型存取元素全靠强制类型转换极易出现运行时类型转换异常且无法约束多参数类型一致性。类型参数 T 的核心目的 1. 编译期锁定统一类型彻底规避手动强转、运行时类型报错 2. 解决「入参、返回值、局部变量需要严格同类型绑定」的场景 3. 实现类全局泛型统一、方法精准类型返回支持链式调用、数据读写复用。核心特征声明位置方法返回值前、类名后方 约束规则只能使用 extends 做上界约束不支持 super读写权限可读可写无集合读写限制类型能力可以作为返回值精准保留子类类型复用性全局可复用类型统一固定2.2 通配符匿名泛型?定义仅能写在泛型容器 List/Map 内部的匿名占位符用于临时限制当前容器的类型范围无名字、无法复用仅对当前这一处容器生效。设计初衷 解决的核心问题仅靠类型参数 T 存在严重短板类型参数强制单一类型统一API 死板、兼容性差、简单场景代码冗余且无法控制集合读写权限。通配符 ? 的核心目的 1. 解决 T 无法兼容「多种子类集合、混合类型集合」的问题提升 API 通用性 2. 简化只读/只写轻量场景无需冗余声明 T精简代码 3. 实现 PECS 读写权限隔离编译期限制集合读写保护原数据安全 4. 解决泛型嵌套、父类容器接收等类型参数难以实现的兼容场景。核心特征书写位置仅限容器尖括号内不能写在方法/类头部约束规则同时支持 ? extends上界、? super下界读写权限存在严格限制? extends 只读、? super 只写类型能力无法返回精确子类读取数据只能得到父类/Object复用性匿名临时生效无法全局复用类型三、类型参数 T 完整详解3.1 标准语法示例泛型方法// T extends Animal类型参数边界声明先定义、后使用 public T extends Animal T getFirst(ListT list) { return list.get(0); }泛型类// 类全局统一泛型类型 T class BoxT { private T data; public T getData() { return data; } public void setData(T data) { this.data data; } }3.2 四大实际开发场景场景1入参与返回值类型统一精准返回子类需求传入子类集合直接返回对应子类对象无需手动强转public T extends Animal T getFirst(ListT list) { return list.get(0); } // 调用测试 ListDog dogList List.of(new Dog()); Dog dog getFirst(dogList); // 直接拿到Dog类型无强转场景2类全局统一泛型通用返回体、工具包装类需求全局统一接口返回格式适配任意业务数据类型// 统一接口返回包装类项目通用 public class ResultT { private Integer code; private String msg; private T data; // 静态泛型方法快速构建成功返回体 public static T ResultT success(T data) { ResultT res new Result(); res.setData(data); res.setCode(200); res.setMsg(操作成功); return res; } // getter/setter 省略 } // 使用测试 ResultDog result Result.success(new Dog()); Dog dog result.getData();场景3集合同时读、同时写拷贝、合并数据通配符 ? extends 会禁止写入操作需要同时读写集合时必须使用类型参数 T// 集合数据拷贝既读源集合又写目标集合 public T extends Animal void copyList(ListT source, ListT target) { for (T animal : source) { target.add(animal); } }场景4多参数强制为同一类型需求约束多个入参为完全相同类型避免类型错乱对比public T boolean sameObj(T a, T b) { return Objects.equals(a, b); }3.3 优缺点总结优点类型统一、支持精准链式返回、集合读写自由、可全局复用缺点API死板单次调用仅支持单一子类集合简单场景使用会造成代码冗余四、通配符 ? 完整详解4.1 两种核心标准语法上界通配符 ? extends X只读含义接收 X 及其所有子类只能读、不能写用于数据安全读取场景public void printAnimals(List? extends Animal list) { for (Animal a : list) { System.out.println(a); } // list.add(new Dog()); // 编译报错禁止写入保护原集合 }下界通配符 ? super X只写含义接收 X 及其所有父类只能写、读取丢失精准类型用于数据收集场景public void addDog(List? super Dog container) { container.add(new Dog()); // 可正常写入子类对象 } // 调用支持多种父类容器兼容性极强 addDog(new ArrayListDog()); addDog(new ArrayListAnimal()); addDog(new ArrayListObject());4.2 三大实际开发场景带完整代码场景1仅遍历读取兼容多种子类集合类型参数 T 不支持混合子类集合通配符可完美兼容所有子类、混合集合public void printAnimals(List? extends Animal list) { list.forEach(System.out::println); } // 全部合法兼容性拉满 printAnimals(List.of(new Dog())); printAnimals(List.of(new Cat())); printAnimals(List.of(new Dog(), new Cat())); // 支持混合子类列表场景2简单只读工具简化代码无多余泛型声明仅做读取操作、无需类型复用时无需声明 T通配符更简洁// 冗余写法没必要使用类型参数 public T extends Animal void print1(ListT list) { list.forEach(System.out::println); } // 简洁优雅写法通配符最优解 public void print2(List? extends Animal list) { list.forEach(System.out::println); }场景3编译期数据保护禁止方法篡改外部集合通过通配符语法特性从编译层面禁止修改原集合避免业务bugpublic void safeRead(List? extends Animal list) { Animal a list.get(0); // ✅ 正常读取数据 list.add(new Dog()); // ❌ 编译报错杜绝误修改原数据 }4.3 优缺点总结优点API兼容性强、代码简洁、可精准控制集合读写权限、支持多子类兼容缺点无法复用类型、不能返回精确子类、集合存在读写限制五、类型参数T vs 通配符? 核心对比总表对比维度类型参数 T通配符 ?是否有名称有自定义代号 T/E/K匿名无名称书写位置类/方法头部 声明仅容器内部 List?边界关键字仅支持 extends? extends / ? super 都支持类型复用能力整个方法/类可反复复用仅当前容器生效无法复用返回精确子类支持直接返回T不支持只能返回父类/Object集合读写权限可读可写无任何限制? extends只读? super只写集合兼容性单次调用仅支持单一子类支持多子类、混合列表传入核心适用场景链式Builder、数据拷贝、统一返回类型、通用泛型类遍历打印、数据导出、只读工具、参数范围限定七、高频错误写法汇总避坑必看错误1方法头部声明使用通配符 ?规则方法/类头部 只能声明命名类型参数T/E禁止使用匿名 ?// 非法语法编译报错 public ? extends Animal Animal test(List? list) {}错误2容器内部写类型参数约束 T extends X规则List 内部只能使用已定义的类型/通配符不能写类型约束声明// 非法语法编译报错 public Animal test(ListT extends Animal list) {}错误3类型参数边界使用 super规则命名类型参数的边界仅支持 extends绝对不支持 super// 非法语法编译报错 public T super Animal void test(ListT list) {}错误4混淆编译期类型与运行时对象链式断链问题核心原因编译期只识别方法返回值声明类型不看运行时实际对象导致子类方法丢失abstract class BaseBuilder { // 返回值为父类类型编译期类型锁定父类 public BaseBuilder name(String name){return this;} } class UserBuilder extends BaseBuilder { public User build(){return new User();} } // 编译报错name()返回BaseBuilder父类无build()方法 new UserBuilder().name(张三).build();八、一句话总结1.T类型参数(Java为了通用性、灵活性连形参类型都设置为变量用来给参数类型起名时统一类型传什么类型返回什么类型读写自由、可复用(整个方法、类都是这一个类型)2.?通配符(是个占位符可以匹配任何类型)用来补救T的“短板”匿名临时过滤兼容多子类(支持多个类型)、限制读写、比T更轻量化CRTP(自引用泛型约束)就有点复杂了我们下个blog再聊。