その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Guice で generics の仮型引数の実体 class (ParameterizedType) を上手いこと inject する

タイトルだけでは何を言っているかよくわかりませんが,

public class Foo<T> {
    private Class<T> clazz;

    ...

    public void something() {
        // 例えばここで clazz を使って何らか処理をする
        System.out.println(clazz);
    }
}

みたいな class があるときに,ここの clazzT の実際の class (ParameterizedType) を上手いこと inject したいと言う話題です.


結論から言うと,TypeLiteral<T> を使うと所望の動作を実現できます.

public class Foo<T> {
    private Class<T> clazz;

    @Inject
    @SuppressWarnings("unchecked")
    public Foo(TypeLiteral<T> type) {
        this.clazz = (Class<T>) type.getRawType();
    }

    public void something() {
        // 例えばここで clazz を使って何らか処理をする
        System.out.println(clazz);
    }
}

こういう風に TypeLiteral を inject してから,TypeLiteral#getRawType() を使うと ParameterizedType を取得することが可能となります.
でもって,

public class App {
    @Inject
    private Provider<Foo<Integer>> foo;

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
            }
        });
        App main = injector.getInstance(App.class);

        main.foo.get().something(); // => class java.lang.Integer
    }
}

というように使うと,コード中のコメントで書いたように class java.lang.Integer,つまり T の実態の class が inject されている様子を見れてめでたしめでたし.

この記事の例では Provider<T> と Constructor Injection でやりましたが,bind() を使う方法でも実現可能でしょう.

補足

この記事の例だと何が嬉しいか良くわからん感じですが,どこかで役に立つタイミングが来るはず…… (というか今日実際に役に立つタイミングが来たのだけれど,その例をそのまま出すわけにもゆかないし,汎用的な例を考えたものの良いのが思いつかなかった)