Java:TypeVariableとParameterizedType

Javaのリフレクションを使用して、ジェネリッククラスに関する情報を得たい。親クラスがジェネリックであるとき、そのフィールド型が実際には何になるのかなどだ。例えば以下のような状況だ。

  public class Value<T extends Number, S> {
    T foo;
    S bar;
    String sample; 
  }  
  public class Id extends Value<Long, String> {        
  }

IdはValueを継承しており、型パラメータとしてLong, Stringを指定しているため、Valueのfooフィールドは実際にはLong型、barフィールドはString型になる。これを検出したい。

それ以前にひどくわかりづらい用語について整理したいと思う。

TypeVariableとは?

その名の通り、型変数である。先の例でいえば、ValueにつけられたT, Sがこれに当たる。TypeVariableには名前があり、かつ上限境界がある。

....
    for (TypeVariable<?> tv: Value.class.getTypeParameters()) {
      System.out.println(tv.getName());
      for (Type type: tv.getBounds()) {
        System.out.println(type);
      }
    }
....

  public static class Value<T extends Number, S> {
    T foo;
    S bar;
    String sample; 
  }

  public static class Id extends Value<Long, String> {        
  }

この結果は以下になる。

T
class java.lang.Number
S
class java.lang.Object

ParameterizedTypeとは?

この用語がまた分かりづらいのだが、ParameterizedTypeとは「型引数を与えられた型」である。「型の定義」ではない。例えば、上述の例では、Idクラスの定義の中の「Value<Long, String>」の部分になる。

  public static class Value<T extends Number, S> {
    T foo;
    S bar;
    String sample; 
  }

  public static class Id extends 「Value<Long, String>」{ <-------- この部分        
  }

Valueはジェネリッククラスとして定義されており、TypeVariableという「変数」を持つのだが、その「変数」に対する「代入操作」のことをParameterizedTypeと称している。TypeというよりはAssignmentとした方が適切だろう。ジェネリッククラスを「呼び出す」あるいは「使う」ときに、その「型変数」に何かしらの型を「代入」する操作のことだ。ともあれ、ParameterizedTypeとは、「そのような型があらかじめ定義されているわけではない」ことに注意する必要がある。

したがって、ParameterizedTypeは、何かしらのジェネリッククラスを「利用する」場面でのみ現れる。上記以外の例としては、例えば以下だ。

class Sample {
  Value<Long, String>value;
}

この場合も「Value<Long, String>」の部分がParameterizedTypeとして現れる。

また、ParameterizedTypeには必ずしも確定したクラスが与えられるわけではないことに注意する。例えば以下だ。

  public static class Value<T extends Number, S> {
    T foo;
    S bar;
    String sample; 
  }
  public static class Value2<S> extends Value<Long, S> {
  }

Value2では「Value<Long, S>」というParameterizedTypeが作成されるのだが、このSは、この時点ではクラスが確定していない。この場合、SというTypeVariableがParameterizedType中に現れる。