天天看點

Java:如何生成類的泛型參數的執行個體?

要生成類的泛型類型參數的執行個體,先要生成類的泛型類型參數的Class對象。比如,對于泛型類Testable<T, S>,它的T、S這兩個類型參數的Class對象:Class<T>, Class<S>。

public class Testable<T, S> {

  private Class<T> tClazz;
  private Class<S> sClazz;

  public Testable() {
  }

  public Class<T> getTClazz() {
    return tClazz;
  }

  public Class<S> getSClazz() {
    return sClazz;
  }
}
           

網上可以找到的一般性的處理方法如下:

public static List<Class<?>> getTypeArgumentClassList(Class<?> genericClass) {
  Type rawType = genericClass.getGenericSuperclass();
  ParameterizedType o = (ParameterizedType) rawType;

  Type[] typeArguments = o.getActualTypeArguments();
  List<Class<?>> typeArgumentClassList = new ArrayList<Class<?>>();

  for (Type t : typeArguments) {
    Class<?> clazz = (Class<?>) t;
    typeArgumentClassList.add(clazz);
  }

  return typeArgumentClassList;
}
           

測試代碼:

public static void doTest() {
    Testable<String, Integer> g = new Testable<String, Integer>();

    for (Class<?> typeArgumentClazz : getTypeArgumentClassList(g.getClass())) {
      System.out.println(typeArgumentClazz + " : \t" + typeArgumentClazz.getCanonicalName());
    }
  }
           

運作後報錯:

Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType

原因是Class.getGenericSuperclass()是去擷取泛型父類的Type對象,而Testable<T, S>沒有泛型父類,是以報錯了。由于JDK中沒有類似于Class.getGenericClass()這樣的方法,是以隻能想别的辦法解決這個問題:

Testable<String, Integer> g = new Testable<String, Integer>() {};

加上{}後,這行代碼的意思是建立Testable<String, Integer>()匿名子類,然後建立這個匿名子類的對象,這樣對這個匿名子類調用Class.getGenericSuperclass()就不會有問題,因為它的泛型父類就是Testable<T, S>。

public static void doTest() {
  Testable<String, Integer> g = new Testable<String, Integer>(){};

  for (Class<?> typeArgumentClazz : getTypeArgumentClassList(g.getClass())) {
    System.out.println(typeArgumentClazz + " : \t" + typeArgumentClazz.getCanonicalName());
  }
}
           

代碼運作結果:

class java.lang.String :   java.lang.String

class java.lang.Integer :   java.lang.Integer

如果測試代碼換成:

public static void doTest() {
  Testable<String, List<?>> g = new Testable<String, List<?>>() {
  };

  for (Class<?> typeArgumentClazz : getTypeArgumentClassList(g.getClass())) {
    System.out.println(typeArgumentClazz + " : \t" + typeArgumentClazz.getCanonicalName());
  }
}
           

代碼報錯:

Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class

此報錯說明,List<?>對應的Type對象實際上是java.lang.reflect.ParameterizedType,不能直接轉化為Class<?>,是以,修改代碼如下:

public static List<Class<?>> getTypeArgumentClassList(Class<?> genericClass) {
  Type rawType = genericClass.getGenericSuperclass();
  ParameterizedType o = (ParameterizedType) rawType;

  Type[] typeArguments = o.getActualTypeArguments();
  List<Class<?>> typeArgumentClassList = new ArrayList<Class<?>>();

  for (Type t : typeArguments) {
    Class<?> clazz = null;

    if (t instanceof Class<?>) {
      clazz = (Class<?>) t;
    } else {
      Type innerType = ((ParameterizedType) t).getRawType();
      clazz = (Class<?>) innerType;
    }
    typeArgumentClassList.add(clazz);
  }
  return typeArgumentClassList;
}
           

運作結果:

class java.lang.String :   java.lang.String

interface java.util.List :   java.util.List

如果測試代碼換成:

public static void doTest() {
  Testable<String, List<?>[]> g = new Testable<String, List<?>[]>() {
  };

  for (Class<?> typeArgumentClazz : getTypeArgumentClassList(g.getClass())) {
    System.out.println(typeArgumentClazz + " : \t" + typeArgumentClazz.getCanonicalName());
  }
}
           

代碼報錯:

Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl cannot be cast to java.lang.reflect.ParameterizedType

此報錯說明,List<?>[]對應的Type對象實際上是java.lang.reflect.GenericArrayType不是java.lang.reflect.ParameterizedType,是以,修改代碼如下:

public static List<Class<?>> getTypeArgumentClassList(Class<?> genericClass) {
  Type rawType = genericClass.getGenericSuperclass();
  ParameterizedType o = (ParameterizedType) rawType;

  Type[] typeArguments = o.getActualTypeArguments();
  List<Class<?>> typeArgumentClassList = new ArrayList<Class<?>>();

  for (Type t : typeArguments) {
    Class<?> clazz = null;

    if (t instanceof Class<?>) {
      clazz = (Class<?>) t;
    } else if (t instanceof ParameterizedType) {
      Type innerType = ((ParameterizedType) t).getRawType();
      clazz = (Class<?>) innerType;
    } else if (t instanceof GenericArrayType) {
      Type compType = ((GenericArrayType) t).getGenericComponentType();
      Type innerType = ((ParameterizedType) compType).getRawType();
      Class<?> compClazz = (Class<?>) innerType;

      clazz = Array.newInstance(compClazz, 0).getClass();
    }
    typeArgumentClassList.add(clazz);
  }
  return typeArgumentClassList;
}
           

運作結果:

class java.lang.String :   java.lang.String

class [Ljava.util.List; :   java.util.List[]

由于List<?>[]是一個數組,它的Class對象的擷取可能通過:

Array.newInstance(compClazz, 0).getClass();

如果測試代碼換成:

public static void doTest() {
  Testable<String, List<String>[][]> g = new Testable<String, List<String>[][]>() {
  };

  for (Class<?> typeArgumentClazz : getTypeArgumentClassList(g.getClass())) {
    System.out.println(typeArgumentClazz + " : \t" + typeArgumentClazz.getCanonicalName());
  }
}
           

代碼報錯:

Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl cannot be cast to java.lang.reflect.ParameterizedType

此報錯的原因是,compType實作上是List<String>[]對應的Type對象,它還是java.lang.reflect.GenericArrayType不是java.lang.reflect.ParameterizedType,因而還需要再做一次((GenericArrayType) t).getGenericComponentType();

但如果是Testable<String, List<String>[][][]>、Testable<String, List<String>[][][][]>、.......,那就不能解決了。對以上代碼重構後,提供完整實作如下:

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public final class GenericTools {

  public static List<Class<?>> getTypeArgumentClassList(Class<?> genericClass) {
    Type rawType = genericClass.getGenericSuperclass();
    if (rawType instanceof Class<?>) {
      return Collections.emptyList();
    }

    ParameterizedType o = (ParameterizedType) rawType;

    Type[] typeArguments = o.getActualTypeArguments();
    List<Class<?>> typeArgumentClassList = new ArrayList<Class<?>>();

    for (Type typeArgument : typeArguments) {
      typeArgumentClassList.add(getTypeArgumentClass(typeArgument));
    }

    return typeArgumentClassList;
  }

  public static Class<?> getTypeArgumentClass(Type typeArgument) {
    if (typeArgument instanceof Class<?>) {
      return (Class<?>) typeArgument;
    } else if (typeArgument instanceof ParameterizedType) {
      Type innerType = ((ParameterizedType) typeArgument).getRawType();
      return getTypeArgumentClass(innerType);
    } else if (typeArgument instanceof GenericArrayType) {
      Type compType = ((GenericArrayType) typeArgument).getGenericComponentType();
      Class<?> compClazz = getTypeArgumentClass(compType);

      if (compClazz != null) {
        return Array.newInstance(compClazz, 0).getClass();
      }
    }

    return null;
  }
}
           

運作結果:

class java.lang.String :   java.lang.String

class [[Ljava.util.List; :   java.util.List[][]

最終,對Testable<T, S>修改如下:

import java.util.List;

public class Testable<T, S> {

  private Class<T> tClazz;
  private Class<S> sClazz;

  @SuppressWarnings("unchecked")
  public Testable() {
    List<Class<?>> clazzList = GenericTools.getTypeArgumentClassList(this.getClass());

    tClazz = (Class<T>) clazzList.get(0);
    sClazz = (Class<S>) clazzList.get(1);
  }

  public Class<T> getTClazz() {
    return tClazz;
  }

  public Class<S> getSClazz() {
    return sClazz;
  }
}
           

這樣就可以擷取到T, S兩個泛型類型的Class對象,為後續對這些泛型參數執行個體化提供了條件。

參考文檔

Java Reflection GenericArrayType Example

https://www.javarticles.com/2016/02/java-reflection-genericarraytype-example.html

How to create a generic array?

https://stackoverflow.com/questions/18581002/how-to-create-a-generic-array

How to create a generic array in Java?

https://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java