Self-bounded types may sound complicated, but they are actually used to prevent subclasses from messing up type parameters. Let's say we have a generic class A
that can accept any type of parameter:
class A<T> {
T property;
void setProperty(T t) { property = t; }
T getProperty() { return property; }
}
Here, A
can be used with any type of parameter, such as A<Integer>
or A<String>
. If we have a class B
that wants to inherit from A
, but only wants to fix the type parameter in A
to B
, we would typically write it like this:
class B extends A<B> {}
This pattern is called the curiously recurring generic pattern. In simple terms, B
inherits from A
, and A
uses B
as the type parameter.
What's the problem?#
The problem is that this approach does not enforce the use of B
itself as the type parameter. For example, someone could write it like this:
class C {}
class B extends A<C> {}
In this case, B
inherits from A
, but uses C
as the type parameter, completely breaking the rule we want.
Solution with self-bounded types#
To solve this problem, we can use self-bounded types:
class A<T extends A<T>> {}
With this change, the generic parameter T
is forced to be a class that extends A<T>
. In other words, when a subclass inherits from A
, it must pass itself as the type parameter.
So now:
class C {}
class B extends A<C> {} // Compilation error
The above code will fail to compile because C
does not extend A<C>
. But the following code is valid:
class B extends A<B> {} // Compilation successful
This way, we ensure that when B
inherits from A
, the type parameter can only be B
itself.
Practical application of self-bounded types: MyBatis-Plus Wrapper#
MyBatis-Plus is a popular ORM framework, and Wrapper
is a commonly used utility class in it for building query conditions. The Wrapper
class and its subclasses (such as QueryWrapper
, UpdateWrapper
) extensively use self-bounded types in their implementation to ensure that the return type matches the caller, thus enabling a fluent API.
Definition of Wrapper
#
Let's take a look at the simplified definition of the AbstractWrapper
class:
public abstract class AbstractWrapper<T, R, This extends AbstractWrapper<T, R, This>> {
// Assume there are some fields and methods
public This eq(R column, Object val) {
// Implementation logic
return (This) this;
}
public This like(R column, Object val) {
// Implementation logic
return (This) this;
}
// Other condition methods...
}
In this definition, This
is a self-bounded type parameter that extends AbstractWrapper
itself. This means that any class that inherits from AbstractWrapper
must pass itself as the type parameter This
. This ensures that the return type of chained method calls is still the concrete subclass type, rather than the base class type.
Implementation of QueryWrapper
#
Now let's take a look at the simplified implementation of QueryWrapper
:
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {
// Additional query conditions can be defined here
}
QueryWrapper
inherits from AbstractWrapper
and passes QueryWrapper<T>
as the third generic parameter This
. This means that when we call methods like eq
or like
, the return type is still QueryWrapper<T>
, ensuring the consistency of the fluent API.
Example code#
Let's say we have a User
table and want to build query conditions using QueryWrapper
:
QueryWrapper<User> query = new QueryWrapper<>();
query.eq("name", "John")
.like("email", "gmail.com");
// Execute the SQL query
List<User> users = userMapper.selectList(query);
In the above code, each eq
and like
method returns a QueryWrapper<User>
object, allowing us to continue chaining other condition methods.
Benefits of self-bounded types#
In this design, the benefits of self-bounded types are evident:
- Type safety: It avoids returning incorrect types. For example, it ensures that the
eq
method ofQueryWrapper
still returnsQueryWrapper
instead of its superclassAbstractWrapper
. - Chained method calls: By using self-bounded types, the design ensures the coherence of method chaining, making the code more concise and intuitive.
In summary#
Self-bounded types are a technique that forces a class to use itself as a type parameter when inheriting, avoiding errors in type passing. Although this approach may seem convoluted, it is very useful in scenarios where strict control of types is required. In practical development, frameworks like MyBatis-Plus achieve more elegant and type-safe API designs by using self-bounded types.
This article is synchronized and updated to xLog by Mix Space.
The original link is https://me.liuyaowen.club/posts/default/20240821and1