GuiceはJavaのGenerics機能をフル活用したタイプセーフなフレームワークです。
私も、当初よりGenerics機能は、Javaの静的型付け言語的良さを強化する素晴らしい機能だと思っていましたが、実用方法としてはCollectionフレームワーク以上のものが思いつきませんでした。
しかし、Guiceはこれ以上ないほどの素晴らしい使いかたでGenericsを使い可能性を示しています。
Genericsの時代はいつか来るだろうと思っていましたが、それもGuiceの登場で案外早く来そうです。
ここでは、Moduleでの処理bind()メソッドと、その戻り値であるAnnotatedBindingBuilderインターフェイスのto()メソッドのソースを読み解いてみます。
そのなかでGnenericsの使い方を学んでいきましょう。
型 | メソッド |
---|---|
AbstractModuleクラス | bind()メソッド |
AnnotatedBindingBuilderインターフェイス | to()メソッド |
開発者が用意するModuleクラス
まずは、利用者が作るべきModule(MyModuleクラス)の作り方です。
public class MyModule extends AbstractModule { protected void configure() { this.bind(Dependency.class).to(DependencyImpl.class); } }
AbstractModuleクラスが提供するbind()メソッドへ、インジェクトポイント(@Inject)を含むクラスのClass型を渡します。
そのbind()メソッドは以下のようになっています。
/** * @see Binder#bind(Class) */ protected <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) { return builder.bind(clazz); }
引数として受け取ったClass型を「テンプレート」にしています。
テンプレートとはGenericsのための型参照変数です。
そして、このメソッドの戻り値型であるAnnotatedBindingBuilderにもGenericsのテンプレートとして引き渡しています。
これによって、AnnotatedBindingBuilderインターフェイスはbind()メソッドに引き渡されたクラス型を知ることが出来ます。
テンプレートのポリモフィズム
次に行われるのは、to()メソッドの呼び出しです。
このto()メソッドは、LinkedBindingBuilderインターフェイスで定義されています。
bind()メソッドの戻り値であるAnnotatedBindingBuilderインターフェイスは、LinkedBindingBuilderを継承したインターフェイスです。
さて、インターフェイス上のシグネチャだけで、Genericsの威力は十分に知ることができます。
そのため、このto()のシグネチャを見ていく事にしましょう。
/** * Binds to another binding with the specified type. */ ScopedBindingBuilder to(Class<? extends T> implementation);
引数として、<? extends T>という記述がありますね?
この部分では、Genericsのちょっとした技が使われています。
<? extends T >というキーワードの記述です。
前提知識として「Genericsのテンプレートではポリモフィズムは考慮さない。」ことを知っておいてください。
つまり、<Object>と記述した場合、どのようなクラスが設定されてもObjectクラスと認識します。
そこで、このように<? >と記述することで、任意のクラスを受け付けることが出来るようになります。
つまり、この<?>は「全てのクラス」を指すワイルドカード*1の指定方法なのです。
さらに「extendsというキーワード」が使われています。
これはワイルドカードで指定されたテンプレートに「制約」(bound)を与える記述です。
- <? extends T>は、クラスTへのキャストが可能なクラスを受け付ける。
- <? super T>は、クラスTの親クラスやクラスTそのものを受け付ける。
これにより、テンプレートにポリモフィズムのような制限を設けることが出来ます。
さてこの実装の結果、冒頭のMyModuleクラスではどのようなことが起こるかというと、、、
this.bind(Dependency.class).to(DependencyImpl.class);
to()の引数に指定したクラス型が、bind()の引数に指定したクラスのサブクラス
または同じクラスでないとコンパイルエラーになるのです。
また、Eclipseなどでは保管機能が有効に働きます。
そのうえ、コード中のほとんどのタイプキャストを不要にします。
XMLを使ったインジェクトでは実行時まで分からないエラーが、コンパイル時に分かることで開発効率は確実に上がると思います。
Genericsを活用してみたくなりませんか!?
【参考資料リンク】
Java Generics概説 by 平鍋さん
型のパラメータ by 藤村光のホームページ