刘耀文

刘耀文

java开发者
github

自己制限型とは、具体的には何ですか?

自己制限型は複雑に聞こえるかもしれませんが、実際にはサブクラスが型パラメータを乱用するのを防ぐためのものです。ジェネリッククラス 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> {}

このパターンは奇妙な再帰ジェネリックと呼ばれます。要するに、BA を継承し、A の中で B を型パラメータとして使用しているということです。

問題はどこにあるのか?#

問題は、この方法では必ずしも B 自体を型パラメータとする必要がないということです。たとえば、次のように書くこともできます:

class C {}

class B extends A<C> {}

これにより、BA を継承していますが、型パラメータに C を使用しており、私たちが望んでいるルールを完全に破っています。

自己制限型の解決策#

この問題を解決するために、自己制限型を使用します:

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

これにより、T というジェネリックパラメータは、A<T> を継承するクラスであることが強制されます。つまり、サブクラスが A を継承する際には、自分自身を型パラメータとして渡さなければなりません。

したがって、今は:

class C {}

class B extends A<C> {} // コンパイルエラー

上記のコードはコンパイルエラーになります。なぜなら、CA<C> を継承していないからです。一方、次のコードは正当です:

class B extends A<B> {} // コンパイル成功

これにより、BA を継承する際に、型パラメータは必ず B 自体であることが保証されます。

自己制限型の実際の応用:MyBatis-Plus Wrapper#

MyBatis-Plus は人気のある ORM フレームワークであり、Wrapperはクエリ条件を構築するための一般的なユーティリティクラスです。Wrapperクラスとそのサブクラス(QueryWrapperUpdateWrapperなど)は、自己制限型を大量に使用しており、返り値の型が呼び出し元と一致するようにして、フルエント 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>> {
    // ここで追加のクエリ条件を定義できます
}

QueryWrapperAbstractWrapperを継承し、第三のジェネリックパラメータThisとしてQueryWrapper<T>を渡します。これにより、eqlikeなどのメソッドを呼び出した場合、返り値の型は引き続き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);

上記のコードでは、eqlikeメソッドのそれぞれの返り値はQueryWrapper<User>オブジェクトであり、他の条件メソッドをチェーンで呼び出すことができます。

自己制限型の役割#

この設計では、自己制限型の役割が非常に明確です:

  1. 型安全性:間違った型の返り値を防ぐことができます。たとえば、QueryWrappereqメソッドがQueryWrapperを返すことを保証し、親クラスのAbstractWrapperではないことを保証します。
  2. メソッドチェーン:自己制限型の使用により、メソッドチェーンの一貫性が保たれ、コードがより簡潔で直感的になります。

まとめ#

自己制限型は、クラスが継承する際に自分自身を型パラメータとして使用する必要があるようにするテクニックです。これにより、型の渡し間違いを防ぐことができます。このような書き方は見た目が複雑に見えるかもしれませんが、厳密な型の制御が必要な場面で非常に役立ちます。実際の開発では、MyBatis-Plus などのフレームワークが自己制限型を使用することで、よりエレガントで型安全な API 設計を実現しています。

この記事は Mix Space から xLog に同期されています。
オリジナルのリンクは https://me.liuyaowen.club/posts/default/20240821and1 です。


読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。