自引用泛型概述
自引用泛型(Self-referential generics / F-bounded polymorphism,很多人也叫 CRTP 风格)指:类型参数的上界本身又引用了这个类型参数,典型形态是:
1 | class Base<T extends Base<T>> { ... } |
含义:T 必须是“某个继承自 Base<T> 的类型”,从而让 Base 在编译期“知道”子类的精确类型。
1 要解决什么问题:父类方法想返回“子类类型”
没用自引用泛型时的问题
1 | class Base { |
你会失去子类特有方法的链式调用:
1 | b.withName("a").onlyInUser(); // 编译不过,因为 withName 返回 Base |
用自引用泛型(让返回类型保持为子类)
1 | abstract class Base<B extends Base<B>> { |
现在:
1 | new UserBuilder() |
链式调用始终保持 UserBuilder 类型。
2 它到底“类型上”在约束什么?
这句:
1 | B extends Base<B> |
表示:B 不是随便的类型,它必须满足:
B是Base<B>的子类型
所以 class UserBuilder extends Base<UserBuilder> 合法;
但 class X extends Base<String> 不合法(String 不是 Base
这种约束让 Base 能安全地把 this 视作 B(通过 self() 或强转),从而在 Base 里写出“返回子类”的 API。
3 为什么需要 self()?能不能直接 (B) this?
可以强转,但不推荐暴露在公共基类里。
方案 A:强转(常见但有风险)
1 | abstract class Base<B extends Base<B>> { |
风险在于:如果有人写了“破坏约束”的继承结构(通过原始类型 raw type 绕过),运行期可能 ClassCastException。
方案 B:子类实现 self(更稳)
1 | abstract class Base<B extends Base<B>> { |
子类返回 this,不需要 unchecked cast,更清晰。
4 典型使用场景
4.1 Builder / Fluent API(最常见)
让基类提供通用链式方法(name、id、tags…),子类还能追加自身方法且不中断链式。
4.2 “可比较/可排序”这类自类型约束(JDK 经典)
Comparable 是个“弱化版”案例:
1 | class MyType implements Comparable<MyType> { ... } |
工具方法里常见约束:
1 | static <T extends Comparable<? super T>> T max(List<? extends T> list) { ... } |
这也是为了兼容继承关系(? super T)。
4.3 框架基类:返回子类、注册子类、DSL
例如:
- ORM/Query DSL:
Query<T extends Query<T>> where(...) - 图结构/节点 API:
Node<N extends Node<N>> addChild(N child)
5 多层继承时怎么写?
你可以把“自类型参数”一路传下去:
1 | abstract class Base<B extends Base<B>> { |
调用:new Concrete().common().mid().only(); 都保持 Concrete。
6 常见坑
6.1 原始类型(raw type)会破坏类型安全
1 | class Bad extends Base { // 使用 raw type |
这会导致泛型约束失效,可能埋下运行期强转问题。尽量禁止 raw type。
6.2 “写错自类型”导致诡异编译错误
1 | class A extends Base<B> { ... } // B 不是 A |
自引用泛型的关键是:参数必须是“自己”(或至少保持一致的自类型),一般写成 extends Base<当前类名>。
6.3 组合(composition)有时比继承更简单
如果只是想链式配置,有时用组合/返回 this 并配合接口也能做,别为了泛型而泛型。
7 一个更完整的“可复用 Builder 基类”例子
1 | abstract class Builder<B extends Builder<B>> { |




