尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Java 中的奇异递归模板模式(CRTP):用 java-design-patterns 项目实战掌握编译期静态多态

发布时间:2026/9/30 2:25:31

资讯中心
01
ARTICLE

Java 中的奇异递归模板模式(CRTP):用 java-design-patterns 项目实战掌握编译期静态多态

Java 中的奇异递归模板模式(CRTP):用 java-design-patterns 项目实战掌握编译期静态多态
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载奇异递归模板模式Curiously Recurring Template PatternCRTP是一种借助「泛型 继承」实现的编译期多态手法基类以某个派生类自身作为泛型参数从而在编译期就让方法签名精确绑定到具体子类型避免虚方法virtual method调用的运行时开销。本文以 java-design-patterns 仓库中的 curiously-recurring-template-pattern 模块为主线结合 MMA 拳手同量级对战 的完整可运行示例与测试用例讲解 CRTP 的声明语法、运行效果、适用场景与权衡取舍读完即可在自己的类型层次设计中复现这套「同类型互操作」的编译期多态方案。CRTP 是什么一个从 C 传入 Java 的「递归泛型」惯用法CRTP 一词源自 C其定义如下一个类X从「以X自身为模板实参的类模板实例化」派生而来。在 Java 中模板参数对应泛型类型参数因此 CRTP 在 Java 里常被称为Recursive Type Bound递归类型界定Recursive Generic递归泛型Static Polymorphism静态多态Mixin Inheritance混入继承其核心声明模式可以抽象成一条链BaseT extends BaseT → BaseDerived → Derived extends BaseDerived也就是说基类Base是一个泛型类它的类型参数T被限定为「继承自Base自身」而每个具体派生类Derived在继承Base时把自己的类型作为泛型实参传进去。最终效果正如 crtp-flowchart.png 所示基类内部的方法可以面向T编程而T在实际使用中被替换为具体的派生类派生类因而可以调用到「来自基类、却针对自己类型定制」的方法——全程在编译期完成不需要虚方法分派。与运行时多态的本质区别传统 Java 多态依赖继承 方法重写在运行时通过虚方法表动态分派CRTP 则把「调用哪个子类的方法」这件事提前到编译期。这意味着方法调用的目标在编译时已确定没有 vtable 查找开销基类方法签名中的参数类型可以是「调用者自己的类型」这是普通继承无法表达的约束。真实世界类比图书馆媒体与拳击赛配对仓库 README 给出了一个直观的图书馆场景系统要管理书Book、DVD、杂志Magazine等多种媒体它们都具备「借出/归还」等公共行为又各有专属属性。若用 CRTP 设计可定义一个泛型基类MediaItemT承载公共方法每种媒体以自身为泛型参数继承它从而在不引入虚方法开销的前提下让共享逻辑被各子类型复用。另一个更贴近本项目示例的类比是 MMA综合格斗赛事运营拳手必须与同一重量级的对手比赛绝不能出现轻量级对阵重量级这种悬殊配对。这正是 CRTP 最典型的用武之地——让fight(T opponent)这类方法只接受与调用者同类型的参数实现「相互可比性」mutual comparability。用一句大白话概括CRTP 保证了类型中的某些方法只接受它自身子类型的参数从而在编译期获得更高效、更类型安全的多态行为。程序化示例MMA 拳手的同量级对战本模块的全部源码位于 curiously-recurring-template-pattern/src/main/java/crtp下面按依赖关系逐层拆解。第一步定义泛型接口 FighterFighter.java 是一个极简的泛型接口T表示对手的类型public interface FighterT { void fight(T t); }注意这里T尚未施加任何约束真正的约束在下一层的MmaFighter中收紧。第二步用递归类型界定定义 MmaFighterMmaFighter.java 是 CRTP 的枢纽类型参数T被限定为T extends MmaFighterT同时实现FighterTSlf4j Data public class MmaFighterT extends MmaFighterT implements FighterT { private final String name; private final String surname; private final String nickName; private final String speciality; Override public void fight(T opponent) { LOGGER.info({} is going to fight against {}, this, opponent); } }这里的要点T extends MmaFighterT是递归类型界定的完整写法——泛型参数必须是MmaFighter的某个具体子类四个final字段name、surname、nickName、speciality由构造器注入配合 Lombok 的Data自动生成 getter、equals/hashCode 与toStringSlf4j则生成LOGGERfight(T opponent)的形参类型被绑定为调用者的具体子类型这是「只能与同量级拳手对战」的编译期保证来源。第三步子类以自身为泛型实参继承仓库提供了三个子类分别对应不同量级。以 MmaBantamweightFighter.java 和 MmaHeavyweightFighter.java 为代表class MmaBantamweightFighter extends MmaFighterMmaBantamweightFighter { public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { super(name, surname, nickName, speciality); } } public class MmaHeavyweightFighter extends MmaFighterMmaHeavyweightFighter { public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { super(name, surname, nickName, speciality); } }MmaLightweightFighter.java 与上述两个类结构完全一致不再赘述。注意一个细节MmaBantamweightFighter与MmaLightweightFighter是包级可见默认访问权限类MmaHeavyweightFighter是public——同包内使用不受影响。第四步组装运行App.java 是程序入口分别让两名轻量级拳手、两名重量级拳手对战public static void main(String[] args) { MmaBantamweightFighter fighter1 new MmaBantamweightFighter(Joe, Johnson, The Geek, Muay Thai); MmaBantamweightFighter fighter2 new MmaBantamweightFighter(Ed, Edwards, The Problem Solver, Judo); fighter1.fight(fighter2); MmaHeavyweightFighter fighter3 new MmaHeavyweightFighter(Dave, Davidson, The Bug Smasher, Kickboxing); MmaHeavyweightFighter fighter4 new MmaHeavyweightFighter(Jack, Jackson, The Pragmatic, Brazilian Jiu-Jitsu); fighter3.fight(fighter4); }运行输出实际时间戳与 LOGGER 前缀依运行环境而异08:42:34.048 [main] INFO crtp.MmaFighter -- MmaFighter(nameJoe, surnameJohnson, nickNameThe Geek, specialityMuay Thai) is going to fight against MmaFighter(nameEd, surnameEdwards, nickNameThe Problem Solver, specialityJudo) 08:42:34.054 [main] INFO crtp.MmaFighter -- MmaFighter(nameDave, surnameDavidson, nickNameThe Bug Smasher, specialityKickboxing) is going to fight against MmaFighter(nameJack, surnameJackson, nickNameThe Pragmatic, specialityBrazilian Jiu-Jitsu)关键机制跨量级对战在编译期就被拒绝如果试图让轻量级拳手去fight一位重量级拳手代码将无法通过编译——这正是 CRTP 的核心价值类型约束在编译期生效而非运行时抛异常。fight的形参被绑定为MmaBantamweightFighter传入MmaHeavyweightFighter属于类型不匹配编译器直接报错。运行与测试验证依赖与打包配置模块的 pom.xml 声明了三个运行时/测试依赖org.slf4j:slf4j-api与ch.qos.logback:logback-classic日志门面与实现支撑Slf4j生成的LOGGERorg.junit.jupiter:junit-jupiter-enginetest 作用域JUnit 5 测试引擎。此外通过maven-assembly-plugin配置了可执行 jar 的打包manifest 主类指向com.iluwatar.crtp.App方便将整个模块打成一键运行的 fat jar。运行方式在仓库根目录执行仓库自带 Maven Wrapper./mvnw -pl curiously-recurring-template-pattern compile exec:java -Dexec.mainClasscrtp.App或直接编译运行./mvnw -pl curiously-recurring-template-pattern test测试如何证明「同量级」约束FightTest.java 完整演示了 CRTP 约束在混合对手列表中的表现它构造一个包含轻量级、重量级、轻量级拳手的混合ListMmaFighter?逐一对一个轻量级拳手发起挑战捕获ClassCastException并把成功对战者收集起来最终断言assertFalse(challenged.isEmpty()); assertTrue(challenged.stream().allMatch(c - c instanceof MmaBantamweightFighter));即只有同为MmaBantamweightFighter的挑战者才能完成对战其余量级在强转时抛出ClassCastException被捕获并记录错误日志。这里揭示了 CRTP 的一个实践细节——当类型信息被擦除、对象被放入MmaFighter?这种通配符容器时运行时的类型检查责任会落到强制转换上而在直接类型引用场景下如App.main中约束完全由编译器保障。另一个测试 AppTest.java 则简单验证App.main可无异常执行。何时使用 CRTP根据模块 README 的归纳适合采用 CRTP 的场景包括需要通过继承扩展类功能但出于效率考虑更偏好编译期多态而非运行时多态想避免虚函数调用的开销同时仍获得多态行为在泛型编程中提供可在编译期选定的函数或策略实现模板元编程思路在对象层级中使用链式方法fluent chaining时出现类型冲突——CRTP 可让链式调用返回正确的子类型而非基类型需要一个参数化类方法能够接受该类的子类作为参数从而作用于所有继承自它的对象希望某些方法只与同类型实例协作例如实现「相互可比性」如ComparableT的典型用法。真实应用场景在模板类库中实现编译期多态接口在性能敏感类库中增强代码复用例如数学计算、嵌入式系统、实时处理程序——这些场景下虚方法开销不可忽视各类 Java 库中Cloneable接口的实现配合clone返回正确子类型。收益与权衡收益Benefits消除虚函数调用开销提升性能在避免多重继承风险的前提下安全复用基类代码在编译期多态场景下获得更高的灵活性与可扩展性。代价Trade-offs泛型与继承交织理解和调试复杂度上升每次实例化都会产生新的类可能导致代码膨胀code bloat灵活性低于运行时多态——行为必须在编译期完全确定。与相关模式的联系工厂方法Factory Method可与 CRTP 联用在不知道具体类型的情况下实例化派生类策略StrategyCRTP 可以实现编译期的策略选择模板方法Template Method结构上相似但模板方法通过继承在运行时变化行为而 CRTP 通过编译期多态实现行为变化——两者对比阅读能更清晰理解「继承 重写」与「递归类型界定」两条技术路线的差异。延伸阅读模块入口文档curiously-recurring-template-pattern/README.md完整源码目录curiously-recurring-template-pattern/src/main/java/crtp测试用例FightTest.java、AppTest.java模式类设计可继续参考《Design Patterns: Elements of Reusable Object-Oriented Software》与《Effective Java》中关于泛型边界bounded type parameter的章节理解递归类型界定的理论基础。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐java-design-patterns 中的 Bloc 模式用 Java 实现响应式状态管理与动态监听器java design patterns 中的 Bloc 模式用 Java 实现响应式状态管理与动态监听器 导读 BlocBusiness Logic Co示例工程教程Java 设计模式实战Async Method Invocation 异步方法调用模式在 java-design-patterns 中的实现Java 设计模式实战Async Method Invocation 异步方法调用模式在 java design patterns 中的实现 异步方法调用A示例工程教程Java 设计模式实战Balking 模式在 java-design-patterns 中的实现与并发应用Java 设计模式实战Balking 模式在 java design patterns 中的实现与并发应用 Balking退缩/迟疑模式是 Java 并发示例工程教程上一篇FastReport免费开源报表工具3小时从零到精通的完整实战指南下一篇Amplication NestJS Logger 模块实战指南统一日志、链路观测与单元测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。