自限定类型到底是啥?

29 天前(已编辑)
/
15
AI 生成的摘要

自限定类型到底是啥?

自限定类型听起来很复杂,但其实是为了防止子类搞乱类型参数。假设我们有一个泛型类 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 类及其子类(如 QueryWrapperUpdateWrapper)在实现中大量使用了自限定类型,确保返回值类型与调用者保持一致,从而实现流式 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 传递。这意味着,当我们调用 eqlike 等方法时,返回值的类型仍然是 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);

在上面的代码中,每个 eqlike 方法返回的都是 QueryWrapper<User> 对象,这样我们就可以继续链式调用其他条件方法。

自限定类型的作用

在这种设计中,自限定类型的作用非常明显:

  1. 类型安全:避免了返回不正确类型的情况。比如,确保 QueryWrappereq 方法返回的仍然是 QueryWrapper,而不是其父类 AbstractWrapper
  2. 链式调用:通过自限定类型的使用,保证了方法链的连贯性,让代码更加简洁、直观。

一句话

自限定类型就是一种让类在继承时必须用自己作为泛型参数的技巧,避免在类型传递中出错。这种写法虽然看着绕,但用在需要严格控制类型的场景下还是很有用的。在实际开发中,像 MyBatis-Plus 这样的框架通过使用自限定类型,实现了更为优雅和类型安全的 API 设计。

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...