Locked History Actions

guice/TypeParameter

型パラメータのある場合

型パラメータのあるインターフェースについてどのようにバインディング指定し、どのようにインスタンスを取得するか? つまり、

interface Test<T> {}
class StringTest implements Test<String> {}
class IntegerTest implements Test<String> {}

があるとしたら、Testというインターフェースについて(@Named等を使わずに)、型パラメータの有無や種類によって異なるバインディングを作成できるのだろうか?

型パラメータを指定しない方法

型パラメータを無視してバインディング指定を行い、インスタンス注入を行う。

package test;
import com.google.inject.*;

public class TypeParameterZero {
  public interface Test<T> {}
  public static class TestA implements Test<String> {}  
  public static class Sample {
    @Inject Test one; // 型パラメータ無視
    void show() {
      System.out.println(one);
    }
  }  
  public static void main(String[]args) {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(Test.class).to(TestA.class); // 型パラメータ無視
      }
    });
    Sample sample = injector.getInstance(Sample.class);
    sample.show();
  }
}

結果は以下になる。

test.TypeParameterZero$TestA@64dc11

もし、注入される側を以下のようにすると、

@Inject Test<String> one;

以下のエラーとなる。Guice側はバインディング時と注入時の型パラメータを把握しているのである。

Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:

1) No implementation for test.TypeParameterZero.test.TypeParameterZero$Test<java.lang.String> was bound.

この場合、バインディング指定を以下のようにすればよい。

  bind(new TypeLiteral<Test<String>>() {}).to(TestA.class);

もちろん以下のようには記述できない。TypeLiteralはこのような場合の代替として使う。

  bind(Test<String>.class).to(TestA.class);

逆に、バインディング時には上記のようにTypeLiteralを用いて型パラメータを指定し、注入側に生の型を使うと、

@Inject Test one;

やはりエラーとなる。

Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:

1) No implementation for test.TypeParameterZero$Test was bound.

型パラメータによる区別

上述のように型パラメータの有無と違いがGuiceでは完全に把握されている。これを使うと、以下のようなことができる。

package test;
import com.google.inject.*;

public class TypeParameterOne {
  public interface Test<T> {}
  public static class TestA implements Test<String> {}
  public static class TestB implements Test<Integer> {}
  public static class TestC implements Test {}  
  public static class Sample {
    @Inject Test<String> one;
    @Inject Test<Integer>two;
    @Inject Test three;
    void show() {
      System.out.println(one + ", " + two + ", " + three);
    }
  }  
  public static void main(String[]args) {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(new TypeLiteral<Test<String>>() {}).to(TestA.class);
        bind(new TypeLiteral<Test<Integer>>() {}).to(TestB.class);
        bind(Test.class).to(TestC.class);
      }
    });
    Sample sample = injector.getInstance(Sample.class);
    sample.show();
  }
}

結果は

test.TypeParameterOne$TestA@1c1ea29, test.TypeParameterOne$TestB@1f436f5, test.TypeParameterOne$TestC@4413ee

Injectorから直接取得

注入ではなく、Injectorから直接取得するにはこうする。

package test;
import com.google.inject.*;

public class TypeParameterTwo {
  public interface Test<T> {}
  public static class TestA implements Test<String> {}
  public static class TestB implements Test<Integer> {}
  public static class TestC implements Test {}  
  public static void main(String[]args) {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(new TypeLiteral<Test<String>>() {}).to(TestA.class);
        bind(new TypeLiteral<Test<Integer>>() {}).to(TestB.class);
        bind(Test.class).to(TestC.class);
      }
    });
    System.out.println(injector.getInstance(Key.get(new TypeLiteral<Test<String>>() {})));
    System.out.println(injector.getInstance(Key.get(new TypeLiteral<Test<Integer>>() {})));
    System.out.println(injector.getInstance(Test.class));
  }
}

結果は、

test.TypeParameterTwo$TestA@1d520c4
test.TypeParameterTwo$TestB@1764be1
test.TypeParameterTwo$TestC@1ef9f1d

ワイルドカードを使った場合

ちなみに、型パラメータとしてワイルドカードを使うとどうなるか?

package test;
import com.google.inject.*;

public class TypeParameterThree {
  public interface Test<T> {}
  public static class TestA implements Test<String> {}
  public static class TestB implements Test<Integer> {}
  public static class TestC implements Test {}  
  public static void main(String[]args) {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(new TypeLiteral<Test<?>>() {}).to(TestA.class);
        bind(new TypeLiteral<Test<Integer>>() {}).to(TestB.class);
        bind(Test.class).to(TestC.class);
      }
    });
    System.out.println(injector.getInstance(Key.get(new TypeLiteral<Test<?>>() {})));
    System.out.println(injector.getInstance(Key.get(new TypeLiteral<Test<Integer>>() {})));
    System.out.println(injector.getInstance(Test.class));    
    System.out.println(injector.getInstance(Key.get(new TypeLiteral<Test<String>>() {})));
  }
}

結果は、

test.TypeParameterThree$TestA@1ef9f1d
test.TypeParameterThree$TestB@1e9cb75
test.TypeParameterThree$TestC@c5c3ac
Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:

1) No implementation for test.TypeParameterThree.test.TypeParameterThree$Test<java.lang.String> was bound.

つまり、ワイルドカード指定は一つの型パラメータとしてしか機能しない。?とIntegerを両方バインドしても矛盾は生じないし、取得する方もきちんと意図したものが取得できている。また、ワイルドカードだからといって、バインドしていない型Stringを指定しても取得することはできない。

参考

ジェネリックスの情報取得を参照のこと