Javaでオブジェクト指向に基づいたコーディングをするために必須であり、最初の難関だったのが型パラメータでした。
型パラメータが必要なケース
以前「 型パラメータに関して」でまとめてブログを書きはしたんですけど、正直わかっておらず頭にはてなマークが並んでいました。
例えば商品マスタと顧客マスタの画面を作り、機能が似通ってる場合、共通の処理などは当然抽象クラスで定義したほうが、コーディング量が少なくなりますし、保守性も向上します。
しかし、JavaはPHPなどと違い型があるため、なかなかこれが難しかったりします。
どんなケースで必要かというと・・
データ自体をクラスで定義し、商品マスタではProductと顧客マスタではCustomerというクラスでデータを入れて引き回す、というようなケースです。
普通に書く場合はList<Product>などと書けばいいのですが、これを抽象クラスで定義する場合、当然具体的なクラスは書けません。
その場合、Tなどと型情報を入れる変数などを定義することが一般的です。
そのため抽象クラスには下記のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public abstract class AbstractClass<T>{ //抽象クラスないに変数として持たせておきます private T entity //インスタンスで型情報の登録 public Abstract(T entity){ this.entity = entity } //以降のメソッドではTが普通に使える public create(T entity){ List<T> entityList = ・・・・ } public List<T> find(){ } } |
上記のクラスを継承した具象クラスは下記のように書きます。
型パラメータの制限
上記は通常の書き方ですが、型に制限をしたいときなどもあると思います。
例えばある基底となるクラス(BaseClassとしましょう。)があり、そのクラスを使いたいときです。
またそうしないとゲッター、セッター、特定のメソッドを書くことができません。(リフレクションという手もありますが・・・)
その場合、TはBaseClassの拡張である、ということを定義しないといけません。そのために下記のように書きます。
1 2 3 |
public abstract class AbstractClass<T> extends BaseClass>{ //以下は先ほどのコードと同じ。 } |
クラスインスタンスの登録
なお上記は通常のクラスを登録していますが、実際にはクラスインスタンスを登録することのほうが多いでしょう。
現状のプロジェクトだとJPAを使っているのでクラスインスタンスがそもそも必要なのですが、それ以外にもそのクラスに関する様々な情報を取得できるという点でクラスインスタンスのほうがただのクラスよりも使えると思います。
その場合、下記のような記述方法になります。
1 2 3 4 5 6 7 |
public abstract class AbstractClass<T extends BaseClass> { private Class<T> entityClass; public AbstractClass(Class<T> entityClass) { this.entityClass = entityClass; } |
ちなみに具象クラスはこのようになります。
1 2 3 4 5 6 |
public class ProductClass extends AbstractClass<Product> { //コンストラクタにてAbstractClassのコンストラクタを呼び出し //具体的な型情報を登録することができます。 public ProductClass() { super(Product.class); } |
メソッド単位での型パラメータ
上記のように書いておけばインスタンス生成時に個別のクラス情報が登録されるので、あとはクラス内でList<T>などとして使いまわすことができます。
ただ、メソッド単位では別のクラスを使いたいなどというときもあるでしょう。
私のケースであったのは、商品マスタに値を登録しようとおもってもProductのほかにProductDTOなどという別のクラスを作っている場合です。
例えばProductにmakerIdなどというメーカーを識別するIDを持たせている場合、画面で表示するときには具体的なメーカー名などが必要になる場合です。
その場合、もともとのProductではなくProductを拡張したProductDTOというクラスも使用しなくてはいけません。
つまり恒常的に使うクラスのほかに、スポット(メソッド単位)で使うようなクラスも動的に使いたい場合の方法を考えなくてはいけません。
メソッド単位のクラスの場合は引数などで型パラメータを使い、下記のように戻り値の前に型を定義してあげれば大丈夫です。
1 2 3 4 5 |
//ここで定義してあげればOK protected <T1> List<String> findEntity(T1 entity1, ) { Integer companyId = Integer.parseInt(queryMap.get("companyId")); } |
T1自体も型を制限したい場合はT1 extends ~などと記述してあげればOKです。
上記理解でも不十分なところは多いと思いますが、これを身につけないとどうしても抽象度の高い実装ができないので何とか頑張って身につけたいところです。