自己制限型のタイプは複雑に聞こえるかもしれませんが、実際にはサブクラスがタイプパラメータを混乱させないようにするためのものです。ジェネリッククラス 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 の呼び出しを実現しています。
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
を継承し、第三のジェネリックパラメータ This
として QueryWrapper<T>
を渡します。これにより、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 デザインを実現しています。
この記事は Mix Space から xLog に同期されています。
オリジナルのリンクは https://me.liuyaowen.club/posts/default/20240821and1 です。