自限定类型到底是啥?
自限定类型听起来很复杂,但其实是为了防止子类搞乱类型参数。假设我们有一个泛型类 A
,可以接收任何类型的参数:
class A<T> {
T property;
void setProperty(T t) { property = t; }
T getProperty() { return property; }
}
这里 A
可以用任何类型的参数,比如 A<Integer>
或 A<String>
。如果我们有一个类 B
,想继承 A
,但只想让 A
里面的类型参数固定为 B
,通常我们会这么写:
class B extends A<B> {}
这个模式叫奇异递归泛型。说白了,就是 B
继承了 A
,而 A
里面又用了 B
作为类型参数。
问题出在哪?
问题是,这种写法并不能强制规定一定要用 B
自己作为类型参数。比如有人可以这么写:
class C {}
class B extends A<C> {}
这样,B
继承了 A
,但类型参数用了 C
,完全打破了我们想要的规则。
自限定类型的解决方案
为了解决这个问题,我们就用自限定类型:
class A<T extends A<T>> {}
这么一改,T
这个泛型参数就被强制要求必须是继承自 A<T>
的类。换句话说,子类在继承 A
的时候,必须把自己作为类型参数传进去。
所以现在:
class C {}
class B extends A<C> {} // 编译失败
上面的代码会编译失败,因为 C
没有继承自 A<C>
。而下面的代码是合法的:
class B extends A<B> {} // 编译通过
这样一来,我们就保证了 B
继承 A
时,类型参数只能是 B
自己。
自限定类型在实际中的应用:MyBatis-Plus Wrapper
MyBatis-Plus 是一个流行的 ORM 框架,而 Wrapper
是它中一个常用的工具类,用于构建查询条件。Wrapper
类及其子类(如 QueryWrapper
、UpdateWrapper
)在实现中大量使用了自限定类型,确保返回值类型与调用者保持一致,从而实现流式 API(fluent API)的调用。
Wrapper
的定义
我们可以看一下 AbstractWrapper
类的简化定义:
public abstract class AbstractWrapper<T, R, This extends AbstractWrapper<T, R, This>> {
// 假设有一些字段和方法
public This eq(R column, Object val) {
// 实现逻辑
return (This) this;
}
public This like(R column, Object val) {
// 实现逻辑
return (This) this;
}
// 其他条件方法...
}
在这个定义中,This
是一个自限定类型参数,继承自 AbstractWrapper
本身。这意味着,任何继承 AbstractWrapper
的类,都必须将自身作为 This
的类型参数传递。这确保了链式调用返回的仍然是具体的子类类型,而不是基类类型。
QueryWrapper
的实现
接着看一下 QueryWrapper
的简化实现:
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {
// 这里可以定义额外的查询条件
}
QueryWrapper
继承自 AbstractWrapper
,并将 QueryWrapper<T>
作为第三个泛型参数 This
传递。这意味着,当我们调用 eq
或 like
等方法时,返回值的类型仍然是 QueryWrapper<T>
,保证了流式 API 的一致性。
示例代码
假设我们有一个 User
表,并想通过 QueryWrapper
构建查询条件:
QueryWrapper<User> query = new QueryWrapper<>();
query.eq("name", "John")
.like("email", "gmail.com");
// 最终执行 SQL 查询
List<User> users = userMapper.selectList(query);
在上面的代码中,每个 eq
和 like
方法返回的都是 QueryWrapper<User>
对象,这样我们就可以继续链式调用其他条件方法。
自限定类型的作用
在这种设计中,自限定类型的作用非常明显:
- 类型安全:避免了返回不正确类型的情况。比如,确保
QueryWrapper
的eq
方法返回的仍然是QueryWrapper
,而不是其父类AbstractWrapper
。 - 链式调用:通过自限定类型的使用,保证了方法链的连贯性,让代码更加简洁、直观。
一句话
自限定类型就是一种让类在继承时必须用自己作为泛型参数的技巧,避免在类型传递中出错。这种写法虽然看着绕,但用在需要严格控制类型的场景下还是很有用的。在实际开发中,像 MyBatis-Plus 这样的框架通过使用自限定类型,实现了更为优雅和类型安全的 API 设计。