自己限定型は複雑そうに見えますが、実はサブクラスが型引数を勝手に選び、型の整合性を崩すのを防ぐための工夫です。任意の型を型引数として受け取れるジェネリッククラスAがあるとします。
class A<T> {
T property;
void setProperty(T t) { property = t; }
T getProperty() { return property; }
}
AにはA<Integer>やA<String>など、任意の型を型引数として指定できます。Aを継承しつつ、Aの型引数をB自身に固定したいクラス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(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を継承し、3番目のジェネリック型パラメータThisにQueryWrapper<T>を渡しています。そのため、eqやlikeなどのメソッドを呼んでも戻り値の型はQueryWrapper<T>のままで、流れるようなAPIとして一貫性が保たれます。
サンプルコード
Userテーブルがあり、QueryWrapperでクエリ条件を構築したいとします。
上のコードでは、eqとlikeはどちらもQueryWrapper<User>型のオブジェクトを返すので、そのまま他の条件メソッドもチェーンできます。
自己限定型の役割
この設計では、自己限定型の働きがはっきりと現れます。
- 型安全性:誤った型が返ることを防ぎます。たとえば、
QueryWrapperのeqメソッドが、基底クラスAbstractWrapperではなくQueryWrapperを返すように保証されます。 - チェーン呼び出し:自己限定型を使うことでメソッドチェーンがつながり、コードが簡潔で直感的になります。
一言でまとめると
自己限定型とは、クラスが親を継承する際に、自身をジェネリック型引数として必ず指定させる手法です。型の伝播でミスを防げます。この書き方は乍見回りくどいものの、型を厳密に制御したい場面ではかなり有効です。実際の開発では、MyBatis-Plusのようなフレームワークが自己限定型を活用することで、よりエレガントで型安全なAPIを実現しています。