ジェネリクスの情報取得方法

ジェネリックス型情報は保持される

一般にJavaではコンパイル時にのみジェネリックス情報が使われ、バイナリ(.class)になった時には消えてしまうと説明されることが多いのだが、そんなことは全くない。
そもそも.classに型パラメータ情報が存在しなければ、それをライブラリとしては使用できなくなってしまう。つまり、

public class Sample<T extends FooBar> {
  public void doSomething(T value);
}

というクラスをコンパイルして.classにし、ソースファイル無しで他人にあげたとする。これを利用する場合にコンパイラには以下のように見えてしまったら困るのである。

public class Sample {
  public void doSomething(FooBar value);
}

取得方法

これらの型情報を取得する簡単な方法を示す。詳細な情報取得は関連するAPIを使いこなすことが必要。

package test;

import java.lang.reflect.*;
import java.util.*;

public class GenericTypes {

  public static class Foo<T> {
    public void doSomething(T t) {      
    }
  }

  public static class Bar extends Foo<Float>{
    public List<String>stringList;
    public List<Integer>convert(Set<String>value) {
      return null;
    }
  }

  public static void main(String[]args) throws Exception {
    System.out.println("\nFooクラス");
    inspect(Foo.class);

    System.out.println("\nBarクラス");
    inspect(Bar.class);

    System.out.println("\nオブジェクト");
    Foo<Double>foo1 = new Foo<Double>();
    inspect(foo1.getClass());

    System.out.println("\n匿名クラス");
    Foo<Double>foo2 = new Foo<Double>() {}; // 注意 !!!
    inspect(foo2.getClass());
  }

  private static void inspect(Class<?>clazz) {
    System.out.println(" 型パラメータ");

    Type superType = clazz.getGenericSuperclass();
    if (superType instanceof ParameterizedType) {
      for (Type type: ((ParameterizedType)superType).getActualTypeArguments()) {
        System.out.println("  " + type);
      }
    } else {
      System.out.println("  ???");
    }

    System.out.println(" フィールド...");
    Field[]fields = clazz.getFields();
    for (Field f: fields) {
      if (f.getDeclaringClass() == Object.class) continue;
      Type type = f.getGenericType();
      System.out.println("  " + type);
    }

    System.out.println(" メソッド...");
    Method[]methods = clazz.getMethods();
    for (Method m: methods) {
      if (m.getDeclaringClass() == Object.class) continue;      
      System.out.println("   " + m);
      Type type = m.getGenericReturnType();
      System.out.println("    Return:" + type);
      Type[]types = m.getGenericParameterTypes();
      for (Type t: types) {
        System.out.println("    Parameter:" + t.toString());
      }
    }
  }
}

実行結果は以下である。


Fooクラス 型パラメータ ??? フィールド... メソッド... public void test.GenericTypes$Foo.doSomething(java.lang.Object) Return:void Parameter:T Barクラス 型パラメータ class java.lang.Float フィールド... java.util.List<java.lang.String> メソッド... public java.util.List test.GenericTypes$Bar.convert(java.util.Set) Return:java.util.List<java.lang.Integer> Parameter:java.util.Set<java.lang.String> public void test.GenericTypes$Foo.doSomething(java.lang.Object) Return:void Parameter:T オブジェクト 型パラメータ ??? フィールド... メソッド... public void test.GenericTypes$Foo.doSomething(java.lang.Object) Return:void Parameter:T 匿名クラス 型パラメータ class java.lang.Double フィールド... メソッド... public void test.GenericTypes$Foo.doSomething(java.lang.Object) Return:void Parameter:T

面白いことに、型パラメータを指定したオブジェクトfoo1ではその型を取得することはできないのだが、Fooを継承した匿名クラス(「注意」とした箇所)では型の取得ができてしまうのである。

型パラメータを利用する例

以下では、OUTという型パラメータに設定されたクラスを取得し、そのクラスの配列を作成する例を示す。
型パラメータとして指定されたクラスを取得し、その配列を作成することができる。

package test;

import java.lang.reflect.*;

public class ArrayTest {

  public static abstract class ArrayConverter<IN, OUT> {

    @SuppressWarnings("unchecked")
    public OUT[] convert(IN[]in) {
      Class<OUT>outClass = getOutClass();
      OUT[]array = (OUT[])java.lang.reflect.Array.newInstance(outClass, in.length);
      for (int i = 0; i < in.length; i++) {
        array[i] = convert(in[i]);
      }
      return array;
    }    

    @SuppressWarnings("unchecked")
    private Class<OUT> getOutClass() {
      Type type = getClass().getGenericSuperclass();
      if (type instanceof ParameterizedType) {
        Type[] argTypes = ((ParameterizedType) type).getActualTypeArguments();
        return (Class<OUT>)argTypes[1];
      } else {
        throw new IllegalStateException();
      }
    }

    public abstract OUT convert(IN in);
  }

  public static void main(String[]args) {

    Integer[]result = new ArrayConverter<String, Integer>() {
      public Integer convert(String value) {
        return Integer.parseInt(value);
      }
    }.convert(new String[] { "1", "2", });

    for (Integer r: result) {
      System.out.println("" + r);
    }
  }
}

Guiceでの利用例

Google Guiceでは匿名クラスを作成することにより、型パラメータ情報を取得している。以下では、fooStr, fooIntに適切なオブジェクトを注入することができる

package test;

import com.google.inject.*;

public class GuiceTest {

  public interface Foo<T> {    
    public void doSomething(T value);
  }
  public static class FooString implements Foo<String> {
    public void doSomething(String value) {      
    }
  }
  public static class FooInteger implements Foo<Integer> {
    public void doSomething(Integer value) {      
    }
  }

  public static class Bar {
    @Inject Foo<String>fooStr;
    @Inject Foo<Integer>fooInt;
    @Override public String toString() {
      return fooStr.getClass() + ", " + fooInt.getClass();
    }
  }

  public static void main(String[]args) {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(new TypeLiteral<Foo<String>>() {}).to(FooString.class);
        bind(new TypeLiteral<Foo<Integer>>() {}).to(FooInteger.class);
      }
    });
    Bar bar = injector.getInstance(Bar.class);
    System.out.println(bar.toString());

  }
}

実行結果は以下のとおり

class test.GuiceTest$FooString, class test.GuiceTest$FooInteger

参考