通过反射获取 Java 中泛型参数的类型

是否有可能获得泛型参数的类型?

举个例子:

public final class Voodoo {
public static void chill(List<?> aListWithTypeSpiderMan) {
// Here I'd like to get the Class-Object 'SpiderMan'
Class typeOfTheList = ???;
}


public static void main(String... args) {
chill(new ArrayList<SpiderMan>());
}
}
223981 次浏览

这是不可能的,因为只有在编译时才会考虑 Java的类型擦除。因此,Java 泛型只是某种预处理器。但是,您可以得到列表成员的实际类。

不,这不可能。

你可以得到一个通用类型的字段给定的类是唯一的例外,该规则,即使这是一个有点黑客。

请参阅 了解 Java 中泛型的类型以获得这方面的示例。

不,这不可能。由于向下兼容性问题,Java 的泛型基于 类型删除,也就是在运行时,您所拥有的只是一个非泛型 List对象。有一些关于运行时类型参数的信息,但是它存在于类定义中(例如,您可以询问“ 这个字段的定义使用什么泛型类型?”) ,而不是在对象实例中。

有一个结构,我曾经偶然发现

Class<T> persistentClass = (Class<T>)
((ParameterizedType)getClass().getGenericSuperclass())
.getActualTypeArguments()[0];

所以似乎有一些反射魔法在周围,不幸的是,我并不完全理解... 对不起。

由于类型擦除,知道列表类型的唯一方法是将类型作为参数传递给方法:

public class Main {


public static void main(String[] args) {
doStuff(new LinkedList<String>(), String.class);


}


public static <E> void doStuff(List<E> list, Class<E> clazz) {


}


}

事实上,我得到了这个工作。考虑以下片段:

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
if( genericParameterTypes[i] instanceof ParameterizedType ) {
Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)"


}
}

我用的是 JDK1.6

正如@bertolami 所指出的,对我们来说,变量类型不可能得到它的未来值(typeOfList 变量的内容)。

不过,您可以像下面这样将类作为参数传递给它:

public final class voodoo {
public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
// Here I'd like to get the Class-Object 'SpiderMan'
Class typeOfTheList = clazz;
}


public static void main(String... args) {
chill(new List<SpiderMan>(), Spiderman.class );
}
}

当您必须将类变量传递给 ActivityInstrumentationTestCase2的构造函数时,这或多或少是 Google 所做的。

我想试着把@DerMike 的答案分解一下来解释:

首先,类型擦除并不意味着 JDK排除了在运行时的类型信息。它是一种允许编译时类型检查和运行时类型兼容性在同一种语言中共存的方法。正如这段代码所暗示的那样,JDK 保留了擦除的类型信息——它只是不与检查过的类型转换和内容相关联。

其次,这将泛型类型信息提供给一个泛型类,而这个泛型类恰好位于被检查的具体类型的层次结构的上一层——也就是说,具有泛型类型参数的抽象父类可以找到与其类型参数对应的具体类型,从而实现 直接从其继承的具体实现。如果这个类是非抽象的并且是实例化的,或者具体的实现在两个级别之下,那么这个类就不会工作(尽管一点点的欺骗可以使它适用于超过一个级别的任何预定数量的级别,或者直到最低级别的具有 X 泛型类型参数的类,等等)。

无论如何,继续解释,这里是代码,为了便于参考分成几行:

1# Class genericParameter0OfThisClass =
2#     (Class)
3#         ((ParameterizedType)
4#             getClass()
5#                .getGenericSuperclass())
6#                    .getActualTypeArguments()[0];

让“我们”成为包含这段代码的泛型类型的抽象类:

  • 第4行获取当前具体类的 Class 实例。这标识了我们的直系后代的具体类型。
  • 第5行获取该类的父类型为 Type; 这就是我们。因为我们是一个参数类型,所以我们可以安全地将自己强制转换为 Parameter terizedType (第3行)。关键在于,当 Java 确定这个 Type 对象时,它使用子对象中的类型信息将类型信息与新的 Parameter terizedType 实例中的类型参数关联起来。现在我们可以访问泛型的具体类型。
  • 第6行按照类代码中声明的顺序获取映射到泛型中的类型数组。对于本例,我们取出第一个参数。这是一个类型。
  • 第2行强制转换返回给 Class 的最终 Type。这样做是安全的,因为我们知道泛型类型参数可以接受什么类型,并且可以确认它们都是类(我不确定在 Java 中如何获得一个没有与其关联的 Class 实例的泛型参数)。

差不多就是这样。因此,我们将类型信息从我们自己的具体实现推回到我们自己,并使用它来访问类句柄。我们可以双倍使用 getGenericSuperclass () ,并进入两个级别,或者消除 getGenericSuperclass () ,并为我们自己获取具体类型的值(注意: 我还没有测试这些场景,它们还没有出现)。

如果您的具体的孩子是任意数量的跳远,或者如果您是具体的,而不是最终的,这是棘手的,特别是如果您希望您的任何(可变深)孩子有自己的类型。但是您通常可以围绕这些考虑因素进行设计,因此这将使您获得最多的方法。

希望这对谁有帮助!我知道这个帖子很老了。我可能会把这个解释删掉,留着回答其他问题。

实际上,通过应用 “匿名课堂”的把戏和来自 超级类型代币的想法,有一个解决方案:

public final class Voodoo {
public static void chill(final List<?> aListWithSomeType) {
// Here I'd like to get the Class-Object 'SpiderMan'
System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
System.out.println(((ParameterizedType) aListWithSomeType
.getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]);
}
public static void main(String... args) {
chill(new ArrayList<SpiderMan>() {});
}
}
class SpiderMan {
}

诀窍在于在原来的(简单的) new ArrayList<SpiderMan>()的地方创建一个 匿名课程new ArrayList<SpiderMan>() {}。使用匿名类(如果可能的话)确保编译器保留有关给类型参数 List<?>的类型参数 SpiderMan的信息。好了!

不能从变量中获得泛型参数,但可以从方法或字段声明中获得:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();

用途:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();

@ DerMike 关于获取参数化接口的通用参数的答案的附录(使用 Java-8缺省方法中的 # getGenericInterfaces ()方法以避免重复) :

import java.lang.reflect.ParameterizedType;


public class ParametrizedStuff {


@SuppressWarnings("unchecked")
interface Awesomable<T> {
default Class<T> parameterizedType() {
return (Class<T>) ((ParameterizedType)
this.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0];
}
}


static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};


public static void main(String[] args) {
System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
// --> Type is: ParameterizedStuff$Beer
}

对于我来说,阅读这段代码很困难,我只是把它分成了两行:

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

我想创建一个 Type 参数的实例,而不需要为我的方法添加任何参数:

publc T getNewTypeInstance(){
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];


// for me i wanted to get the type to create an instance
// from the no-args default constructor
T t = null;
try{
t = c.newInstance();
}catch(Exception e){
// no default constructor available
}
return t;
}

您可以得到具有反射的泛型参数的类型,就像我在这个例子中找到的 给你:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;


public class Home<E> {
@SuppressWarnings ("unchecked")
public Class<E> getTypeParameterClass(){
Type type = getClass().getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) type;
return (Class<E>) paramType.getActualTypeArguments()[0];
}


private static class StringHome extends Home<String>{}
private static class StringBuilderHome extends Home<StringBuilder>{}
private static class StringBufferHome extends Home<StringBuffer>{}


/**
* This prints "String", "StringBuilder" and "StringBuffer"
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Object object0 = new StringHome().getTypeParameterClass().newInstance();
Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
System.out.println(object0.getClass().getSimpleName());
System.out.println(object1.getClass().getSimpleName());
System.out.println(object2.getClass().getSimpleName());
}
}

我已经为期望接受或返回 Iterable<?...>的方法编写了这个代码:

/**
* Assuming the given method returns or takes an Iterable<T>, this determines the type T.
* T may or may not extend WindupVertexFrame.
*/
private static Class typeOfIterable(Method method, boolean setter)
{
Type type;
if (setter) {
Type[] types = method.getGenericParameterTypes();
// The first parameter to the method expected to be Iterable<...> .
if (types.length == 0)
throw new IllegalArgumentException("Given method has 0 params: " + method);
type = types[0];
}
else {
type = method.getGenericReturnType();
}


// Now get the parametrized type of the generic.
if (!(type instanceof ParameterizedType))
throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
ParameterizedType pType = (ParameterizedType) type;
final Type[] actualArgs = pType.getActualTypeArguments();
if (actualArgs.length == 0)
throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);


Type t = actualArgs[0];
if (t instanceof Class)
return (Class<?>) t;


if (t instanceof TypeVariable){
TypeVariable tv =  (TypeVariable) actualArgs[0];
AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
return (Class) tv.getAnnotatedBounds()[0].getType();
}


throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}

对于 提问的快速回答是不可以,因为 Java 泛型类型会被删除。

更长的回答是,如果你已经像这样创建了你的列表:

new ArrayList<SpideMan>(){}

然后在这种情况下,泛型类型保留在上面新的匿名类的泛型超类中。

我并不建议对列表这样做,但它是一个侦听器实现:

new Listener<Type>() { public void doSomething(Type t){...}}

由于推断了 JVM 之间超类和超接口的泛型类型的变化,所以泛型解决方案并不像有些答案所暗示的那样直截了当。

现在我做到了

下面是另一个技巧。使用通用的 vararg 数组

import java.util.ArrayList;


class TypedArrayList<E> extends ArrayList<E>
{
@SafeVarargs
public TypedArrayList (E... typeInfo)
{
// Get generic type at runtime ...
System.out.println (typeInfo.getClass().getComponentType().getTypeName());
}
}


public class GenericTest
{
public static void main (String[] args)
{
// No need to supply the dummy argument
ArrayList<Integer> ar1 = new TypedArrayList<> ();
ArrayList<String> ar2 = new TypedArrayList<> ();
ArrayList<?> ar3 = new TypedArrayList<> ();
}
}

我注意到许多人倾向于 getGenericSuperclass()解决方案:

class RootGeneric<T> {
public Class<T> persistentClass = (Class<T>)
((ParameterizedType)getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}

但是,这个解决方案很容易出错。如果子代中有泛型,那么 没有就会正常工作。考虑一下:

class Foo<S> extends RootGeneric<Integer> {}


class Bar extends Foo<Double> {}

Bar.persistentClass会有哪种类型?Class<Integer>?不,是 Class<Double>。这是因为 getClass()总是返回最上面的类,在本例中是 Bar,它的泛型超类是 Foo<Double>。因此,参数类型将是 Double

如果你需要一个不会失败的可靠解决方案,我可以提出两个。

  1. 使用 Guava。它有一个正是为此目的而设计的类: com.google.common.reflect.TypeToken。它可以很好地处理所有的角落案件,并提供了一些更好的功能。不利的一面是额外的依赖性。假设您已经使用了这个类,那么您的代码将看起来简单而清晰,如下所示:
class RootGeneric<T> {
@SuppressWarnings("unchecked")
public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. 使用下面的自定义方法。它实现了一个类似于上面提到的 Guava 类的显著简化的逻辑。但是,我不能保证它容易出错。不过,它确实解决了通用子代的问题。
abstract class RootGeneric<T> {
@SuppressWarnings("unchecked")
private Class<T> getTypeOfT() {
Class<T> type = null;
Class<?> iter = getClass();
while (iter.getSuperclass() != null) {
Class<?> next = iter.getSuperclass();
if (next != null && next.isAssignableFrom(RootGeneric.class)) {
type =
(Class<T>)
((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
break;
}
iter = next;
}
if (type == null) {
throw new ClassCastException("Cannot determine type of T");
}
return type;
}
}