Self-bounded types may sound complicated, but their purpose is simply to prevent subclasses from getting the type arguments wrong. Suppose we have a generic class A that accepts a type argument of any kind:
class A<T> {
T property;
void setProperty(T t) { property = t; }
T getProperty() { return property; }
}
Here, A can be used with any type argument, such as A<Integer> or A<String>. Suppose we have a class B that extends A but wants to fix the type argument of A to B. We would normally write:
class B extends A<B> {}
This pattern is called curiously recurring generics. Put simply, B extends A, and A uses B as its type argument.
Where Does the Problem Come?
The problem is that this form does not actually enforce that B itself must be used as the type argument. For example, someone could write:
class C {}
class B extends A<C> {}
That allows B to extend A with C as the type argument, completely undermining the rule we wanted.
The Self-Bounded Type Solution
To solve this problem, we use a self-bounded type:
class A<T extends A<T>> {}
With this change, the generic parameter T is required to be a type that extends A<T>. In other words, when a subclass extends A, it must pass itself as the type argument.
So now:
class C {}
class B extends A<C> {} // 编译失败
The code above fails to compile because C does not extend A<C>. By contrast, the following code is valid:
class B extends A<B> {} // 编译通过
This ensures that when B extends A, the type argument can only be B itself.
Self-Bounded Types in Practice: The MyBatis-Plus Wrapper
MyBatis-Plus is a popular ORM framework, and Wrapper is one of its commonly used utility classes for building query conditions. The Wrapper class and its subclasses, such as QueryWrapper and UpdateWrapper, make extensive use of self-bounded types in their implementations. This keeps the return type consistent with the caller, enabling calls through a fluent API.
Definition of Wrapper
Here's a simplified definition of the AbstractWrapper class:
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;
}
// 其他条件方法...
}
In this definition, This is a self-bounded type parameter that extends AbstractWrapper itself. In practice, this means that any class extending AbstractWrapper must pass itself as the type argument for This. That ensures chained calls still return the concrete subclass type rather than the base type.
Implementation of QueryWrapper
Next, here's a simplified implementation of QueryWrapper:
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {
// 这里可以定义额外的查询条件
}
QueryWrapper extends AbstractWrapper and passes QueryWrapper<T> as the third generic argument, This. As a result, when we call methods such as eq or like, the return type is still QueryWrapper<T>, keeping the fluent API consistent.
Example Code
Suppose we have a User table and want to build query conditions with QueryWrapper:
QueryWrapper<User> query = new QueryWrapper<>();
query.eq("name", "John")
.like("email", "gmail.com");
// 最终执行 SQL 查询
List<User> users = userMapper.selectList(query);
In the code above, each eq and like call returns a QueryWrapper<User> object, so we can continue chaining other condition methods.
What Self-Bounded Types Do
In this design, the role of self-bounded types is clear:
- Type safety: They prevent incorrect return types. For example, they ensure that calling
eqon aQueryWrapperstill returns aQueryWrapper, not its parent class,AbstractWrapper. - Chained calls: By using self-bounded types, method chains remain consistent, making the code cleaner and more intuitive.
In a Nutshell
A self-bounded type is a technique that requires a class to pass itself as the generic argument when extending another class, helping prevent mistakes as types are passed around. It may look convoluted at first, but it is useful when you need strict control over types. In real-world development, frameworks such as MyBatis-Plus use self-bounded types to deliver a more elegant, type-safe API design.