万籁俱寂,万字将成。
刘耀文
Stay hungry. Stay foolish.
© 2024-2026
Powered by Mix Space&
余白 / Yohaku
.
正在被0人看爆
关于
关于本站关于我
更多
时间线友链
联系
写留言发邮件 ↗
刘耀文
Stay hungry. Stay foolish.
链接
关于本站·关于我·时间线·友链·写留言·发邮件
© 2024-2026 Powered by Mix Space&
余白 / Yohaku
.
正在被0人看爆
赣ICP备2024031666号
RSS 订阅·站点地图·
··|
RSS 订阅·站点地图·|··|赣ICP备2024031666号
稍候片刻,月出文自明。

自限定类型到底是啥?

(已编辑)
/
42
AI·GEN

关键洞察

这篇文章上次修改于,可能部分内容已经不适用,如有疑问可询问作者。

自限定类型到底是啥?

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • 自限定类型听起来很复杂,但其实是为了防止子类搞乱类型参数。假设我们有一个泛型类 A,可以接收任何类型的参数:

    JAVA
    class A<T> {
        T property;
        void setProperty(T t) { property = t; }
        T getProperty() { return property; }
    }
    

    这里 A 可以用任何类型的参数,比如 A<Integer> 或 A<String>。如果我们有一个类 B,想继承 A,但只想让 A 里面的类型参数固定为 B,通常我们会这么写:

    JAVA
    class B extends A<B> {}
    

    这个模式叫奇异递归泛型。说白了,就是 B 继承了 A,而 A 里面又用了 B 作为类型参数。

    问题出在哪?

    问题是,这种写法并不能强制规定一定要用 B 自己作为类型参数。比如有人可以这么写:

    JAVA
    class C {}
    
    class B extends A<C> {}
    

    这样,B 继承了 A,但类型参数用了 C,完全打破了我们想要的规则。

    自限定类型的解决方案

    为了解决这个问题,我们就用自限定类型:

    JAVA
    class A<T extends A<T>> {}
    

    这么一改,T 这个泛型参数就被强制要求必须是继承自 A<T> 的类。换句话说,子类在继承 A 的时候,必须把自己作为类型参数传进去。

    所以现在:

    CodeBlock Loading...

    上面的代码会编译失败,因为 C 没有继承自 A<C>。而下面的代码是合法的:

    CodeBlock Loading...

    这样一来,我们就保证了 B 继承 A 时,类型参数只能是 B 自己。

    自限定类型在实际中的应用:MyBatis-Plus Wrapper

    MyBatis-Plus 是一个流行的 ORM 框架,而 Wrapper 是它中一个常用的工具类,用于构建查询条件。Wrapper 类及其子类(如 QueryWrapper、UpdateWrapper)在实现中大量使用了自限定类型,确保返回值类型与调用者保持一致,从而实现流式 API(fluent API)的调用。

    Wrapper 的定义

    我们可以看一下 AbstractWrapper 类的简化定义:

    CodeBlock Loading...

    在这个定义中,This 是一个自限定类型参数,继承自 AbstractWrapper 本身。这意味着,任何继承 AbstractWrapper 的类,都必须将自身作为 This 的类型参数传递。这确保了链式调用返回的仍然是具体的子类类型,而不是基类类型。

    QueryWrapper 的实现

    接着看一下 QueryWrapper 的简化实现:

    CodeBlock Loading...

    QueryWrapper 继承自 AbstractWrapper,并将 QueryWrapper<T> 作为第三个泛型参数 This 传递。这意味着,当我们调用 eq 或 like 等方法时,返回值的类型仍然是 QueryWrapper<T>,保证了流式 API 的一致性。

    示例代码

    假设我们有一个 User 表,并想通过 QueryWrapper 构建查询条件:

    CodeBlock Loading...

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

    自限定类型的作用

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

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

    一句话

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

    JAVA
    class C {}
    
    class B extends A<C> {} // 编译失败
    
    JAVA
    class B extends A<B> {} // 编译通过
    
    JAVA
    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;
        }
        
        // 其他条件方法...
    }
    
    JAVA
    public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {
        // 这里可以定义额外的查询条件
    }
    
    JAVA
    QueryWrapper<User> query = new QueryWrapper<>();
    query.eq("name", "John")
         .like("email", "gmail.com");
         
    // 最终执行 SQL 查询
    List<User> users = userMapper.selectList(query);