Unknown Region

プログラムでハマったエラーとその解決方法についての備忘録メモ

【Java】親に持つGenerics(総称型)<T>のClassを取得する

備忘録がてらに、こちらへメモ。

 

以下の様なクラス構成に於ける「Generics(総称型)<T>」のクラスの取得方法について。

Hoge.java

/**
 * Hogeクラス
 */
public class Hoge {
}

BaseController.java

/**
 * ベースクラスでGenerics(総称型<T>)を持つ
 */
public abstract class BaseController<T> {
}

HogeController.java

/**
 * BaseControllerを継承した子クラスで、Hogeを扱う
 */
public class HogeController extends BaseContainer<Hoge> {
}

ここでやりたいことは、BaseController内からGenerics(総称型)<T>が何のクラスなのかを突き止めたいというもの。

特にインターネット上では「Generics(総称型)<T>のインスタンスを作るにはどうしたら良いか?」という質問をチラホラ見かける。

Javaでは「new T()」はできないというのが常識ですが、裏技的な方法でクラスを取得することは可能。

クラスさえ取得できれば、リフレクションを駆使してインスタンスを作ることもできるかもしれない。

 

さてさて本題に入る。

Generics(総称型)<T>」のクラス取得処理を実現する方法は2つある。

  1. 親のコンストラクタに「Tの可変長引数」を使用する
  2. リフレクションでGenerics(総称型)そのものを取得する

 

1. 親のコンストラクタに「Tの可変長引数」を使用する

可変長引数は「メソッドの引数の数を指定せず、任意に取る様にできる」機能のこと。

引数は[(クラス)... 変数]の形で指定し、メソッド内でその変数は「指定したクラスの配列」として利用することができる。

以下の例ではコンストラクタにこれを利用し「引数を0個与える」ことで「Generics(総称型)<T>の配列」を取得する。

配列からは「それが何のクラスの配列なのか」を取得できるので、「Generics(総称型)<T>」の正体のクラスを取得することができる。

可変長引数を利用した例: BaseController.java

@SuppressWarnings("unchecked")
public abstract class BaseController<T> {

  // Tのクラスを格納するフィールド
  public Class<T> genericsClazz = null;
  
  /**
   * コンストラクタ
   * 
   * @param args
   */
  public BaseController(T... args) {
    this.genericsClazz = (Class<T>) args.getClass().getComponentType();
  }
}

 

この状態でHogeControllerのインスタンスを作成し、genericsClazzを取得すると以下の様になる。

テスト実行プログラム: Demo.java

HogeController hogeController = new HogeController();
System.out.println("genericsClazz: " + hogeController.genericsClazz.getName()); // genericsClazz: Hoge

ただしこの方法は、場合によっては使用できないため注意が必要。

特に「コンストラクタが複数ある場合で、別のコンストラクタからこのコンストラクタを呼んだ場合」、取得できるのは「Hoge」ではなく「Object」になってしまう。

 

可変長引数の利用が上手く行かない例: BaseController.java

@SuppressWarnings("unchecked")
public abstract class BaseController<T> {

  // Tのクラスを格納するフィールド
  public Class<T> genericsClazz = null;

  /**
   * コンストラクタ
   * 
   * @param name
   */
  public BaseController(String name) {
    this();
  }
  
  /**
   * コンストラクタ
   * 
   * @param args
   */
  public BaseController(T... args) {
    this.genericsClazz = (Class<T>) args.getClass().getComponentType();
  }
}

可変長引数の利用が上手く行かない例:: HogeController.java

public class HogeController extends BaseContainer<Hoge> {
  /**
   * コンストラクタ
   * 
   * @param name
   */
  public HogeController(String name) {
    super(name);
  }
}

可変長引数の利用が上手く行かない例: Demo.java

public class Demo {
  /**
   * メインプログラム
   *
   */
  public static void main(String[] args) {
    HogeController hogeController = new HogeController("piyo");
    System.out.println("genericsClazz: " + hogeController.genericsClazz.getName()); // genericsClazz: java.lang.Object
  }
}

ただ以下のパターンでは取得できたので、同じクラス内での別のコンストラクタ呼び出しをする場合だけNGなのだろうか……?

 

うまく行かなさそうでうまく行く例: HogeController.java

/**
 * BaseControllerを継承した子クラス
 */
public class HogeController extends BaseController {
  public HogeController(String name) {
    super();
  }
}

 

2. リフレクションでGenerics(総称型)そのものを取得する

こちらについては解説を省きますが、1に比べると使用できる状況に制限はない。

ただリフレクションなので実行速度が気になるところ。

Java8以降で使用できるStreamAPIを利用しているが、別に利用しない用に書き換えることは可能。

 

リフレクションを利用した例: BaseController.java

@SuppressWarnings("unchecked")
public abstract class BaseController<T> {
  public Class genericsClazz = Stream.iterate((Object) this.getClass(), clazz -> (Object) ((Class<?>) clazz).getSuperclass())
          .map(Objects::requireNonNull)
          .filter(Class.class::isInstance)
          .map(Class.class::cast)
          .map(Class::getGenericSuperclass)
          .filter(ParameterizedType.class::isInstance)
          .map(ParameterizedType.class::cast)
          .map(ParameterizedType::getActualTypeArguments)
          .filter(types -> types.length != 0)
          .map(types -> types[0])
          .filter(Class.class::isInstance)
          .map(Class.class::cast)
          .findFirst()
          .get();
}

テスト実行プログラム: Demo.java

HogeController hogeController = new HogeController();
System.out.println("genericsClazz: " + hogeController.genericsClazz.getName()); // genericsClazz: Hoge

 

探せば他にももっと良い方法があるのかもしれない。

久々にこんな長文を書いた気がする

 

連絡先: plugout777★yahoo.co.jp (クローラー対策のため★を@に変更してください)