1. 概要

このチュートリアルでは、スーパータイプトークンに精通し、実行時にジェネリックタイプ情報を保持するのにどのように役立つかを確認します。

2. イレイジャー

特定の型情報をメソッドに伝達する必要がある場合があります。 たとえば、ここでは、JacksonからJSONバイト配列を String:に変換することを期待しています。

byte[] data = // fetch json from somewhere
String json = objectMapper.readValue(data, String.class);

この期待値は、リテラルクラストークン(この場合は、 String.class。 

ただし、ジェネリック型に同じ期待値を簡単に設定することはできません。

Map<String, String> json = objectMapper.readValue(data, Map<String, String>.class); // won't compile

Javaは、コンパイル中に汎用型情報を消去します。 したがって、汎用タイプのパラメータはソースコードのアーティファクトにすぎず、実行時には存在しません。

2.1. 具象化

技術的に言えば、ジェネリック型はJavaでは具体化されていません。 プログラミング言語の用語では、実行時に型が存在する場合、その型は具体化されていると言います。

Javaで修正されたタイプは次のとおりです。

  • longなどの単純なプリミティブ型
  • StringRunnableなどの非一般的な抽象化
  • ListHashMapなどの生のタイプ
  • すべてのタイプが次のような無制限のワイルドカードである一般的なタイプリスト>また HashMap, ?>
  • などの他のreifiedタイプの配列 String []、int []、List []、 また地図, ?> []

したがって、次のようなものは使用できません地図 。クラスなぜなら地図改良型ではありません。

3. スーパータイプトークン

結局のところ、Javaの匿名内部クラスの機能を利用して、コンパイル時に型情報を保持できます。

public abstract class TypeReference<T> {

    private final Type type;

    public TypeReference() {
        Type superclass = getClass().getGenericSuperclass();
        type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
    }

    public Type getType() {
        return type;
    }
}

このクラスは抽象であるため、そこからサブクラスを派生させることしかできません。

たとえば、匿名の内部を作成できます。

TypeReference<Map<String, Integer>> token = new TypeReference<Map<String, String>>() {};

コンストラクターは、型情報を保持するために次の手順を実行します。

  • まず、この特定のインスタンスの汎用スーパークラスメタデータを取得します。この場合、汎用スーパークラスは次のようになります。 TypeReference
    >>

  • 次に、汎用スーパークラスの実際の型パラメーターを取得して格納します。この場合は、次のようになります。 地図

汎用タイプ情報を保持するためのこのアプローチは、通常、スーパータイプトークンとして知られています。

TypeReference<Map<String, Integer>> token = new TypeReference<Map<String, Integer>>() {};
Type type = token.getType();

assertEquals("java.util.Map<java.lang.String, java.lang.Integer>", type.getTypeName());

Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
assertEquals("java.lang.String", typeArguments[0].getTypeName());
assertEquals("java.lang.Integer", typeArguments[1].getTypeName());

スーパータイプトークンを使用すると、コンテナタイプは次のようになります。 地図、 また、そのタイプパラメータは整数。 

このパターンは非常に有名であるため、JacksonのようなライブラリやSpringのようなフレームワークには独自の実装があります。 JSONオブジェクトを解析して地図スーパータイプトークンを使用してそのタイプを定義することで実現できます。

TypeReference<Map<String, String>> token = new TypeReference<Map<String, String>>() {};
Map<String, String> json = objectMapper.readValue(data, token);

4. 結論

このチュートリアルでは、スーパータイプトークンを使用して、実行時にジェネリックタイプ情報を保持する方法を学習しました。

いつものように、すべての例はGitHubから入手できます。