JavaでジェネリクスのClass(型パラメータ)を取得する

先日のJacksonの件の続き。ジェネリクスを利用している場合に型情報が上手く取得できずに悩んだのでメモしておく。

 

kdnakt.hatenablog.com

 

 

[やりたいこと]

Javaジェネリクスで型パラメータのClass型を取得したい。使いみちとしては、JacksonのObjectMapper#readValue(String content, Class<T> valueType)での2番目の引数として利用したい。

 

アホなので最初は以下のように書いてコンパイルエラーを出していたが上手くいかず。

public abstract class Container<T extends Object> {
    public void print() {
        ObjectMapper mapper = new ObjectMapper();
        T t = mapper.readValue("何かいい感じの文字列", T.class);// エラー
        // tをごにょごにょする
    }
}

 

[コンストラクタでClass型を渡す]

最初に思いついたのがこのやり方。

public abstract class Container<T extends Object> {
    private Class<T> clazz;
    public Container(Class<T> clazz) {
        this.clazz = clazz;
    }
}

ただしこのやり方だと、子クラスで毎回コンストラクタを実装する必要がある。

public class MyContainer extends Container<MyData> {
    public MyContainer() {
        super(MyData.class);
    }
}

 

毎回コンストラクタを実装するのも面倒くさいしなんか冗長だし、もっといい方法はないか、とググると可変長引数を利用するスマートな方法が紹介されていた。なるほど。

 

language-and-engineering.hatenablog.jp

ちなみに自分の作っているのも、具体的なアプリケーションというよりはフレームワーク的な部分なので、この記事で言及されているケースに該当しそう。

そしてScalaならこの辺り悩まなくていいらしい。ちょっと興味出てきた。ただ、その便利さを理解するにはJavaそのものをもう少し理解しないといけない気もする。むむ……。

 

可変長引数を扱う具体的なコードはリンク先のこちらに記載されていた。

nagise.hatenablog.jp

public abstract class Container<T extends Object> {
    private Class<T> clazz;
    public Container(T... t) {
        @SuppressWarnings("unchecked")
        Class<T> clazz = (Class<T>) t.getClass().getComponentType();
        this.clazz = clazz;
    }
}

 

[Class#getGenericSuperclass()を利用する]

他にGoogle先生が教えてくれた例としてはJavaの言語機能のみで実現する方法と、Spring Frameworkを利用する方法がある。

stackoverflow.com

xebia.com

 

public abstract class Container<T extends Object> {
    private Class<T> clazz;
    @SuppressWarnings("unchecked")
    public Container() {
        this.clazz = ((Class<T>) ((ParameterizedType)
                getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
    }
}

getActualTypeArguments()の戻り値の配列に[0]でアクセスしてるのがちょっと気持ち悪い気もする。長さチェックとかした方がいいのかな。でもクラスの実装変えない限りは特に問題なさそう。

 

[Spring Frameworkを利用する]

Spring Frameworkを利用することもできる。コードは先ほどとほぼ同じだが配列へのインデックスアクセスがないぶんやや見た目が良い。

Spring Frameworkを使っている場合はこのやり方が良さそう。

import org.springframework.core.GenericTypeResolver;
public abstract class Container<T extends Object> {
    private Class<T> clazz;
    @SuppressWarnings("unchecked")
    public Container() {
        this.clazz = (Class<T>) GenericTypeResolver
                .resolveTypeArgument(getClass(), Container.class);
    }
}

 

[まとめ]

github.com