使用反射获取Java中通用参数的类型


143

是否可以获取通用参数的类型?

一个例子:

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>());
    }
}

Answers:


156

我曾经偶然发现的一种构造看起来像

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

因此,似乎有些不可思议的想法使我感到不幸,我很不了解...对不起。


2
但是,然后将其声明为抽象的。当您要使用它时,请对它进行内联子类化。
Snicolas 2012年

42
尽管这已经很老了并且由于某种原因被接受了,但我还是拒绝了,因为它根本无法回答问题。在问题中给出的示例中,它不会给出“ SpiderMan”。在某些情况下,它无疑是有用的,但不适用于所提出的问题。
乔恩·斯基特

15
到目前为止,没有人会接受“无法完成”的答案。我们在这里是因为我们绝望。
艾迪生

14
我有一个例外:Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType 不确定什么是约束。

4
像@Dish一样,我得到了一个java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Kenny Wyland '16

133

我想尝试分解来自@DerMike的答案以解释:

首先,类型擦除并不意味着JDK 在运行时消除了类型信息。这是一种允许编译时类型检查和运行时类型兼容性以相同语言共存的方法。正如该代码块所暗示的那样,JDK保留了已擦除的类型信息-只是与已检查的强制类型转换和内容无关。

其次,这将泛型类型信息提供给泛型类,该泛型类恰好位于要检查的具体类型的层次结构之上,即具有泛型类型参数的抽象父类可以找到与其类型参数相对应的具体类型,以实现自身的具体实现即直接从它继承。如果这个类不是抽象的并且被实例化的,或者具体的实现是降低了两个级别,那么这是行不通的(尽管有些麻烦可以使它应用于除一个级别之外的任何预定数量的级别,或者适用于最低级别的级别。带有X个通用类型参数等)。

无论如何,继续解释。这是再次的代码,为了便于参考,将其分成几行:

1#类genericParameter0OfThisClass = 
2#(班级)
3#((参数化类型)
4#getClass()
5#.getGenericSuperclass())
6#.getActualTypeArguments()[0];

假设“我们”是包含此代码的泛型类型的抽象类。从内到外大致阅读:

  • 第4行获取当前具体类的Class实例。这确定了我们直系后代的具体类型。
  • 第5行获取该类的超类型作为Type;这是我们。由于我们是参数类型,因此我们可以安全地将自己强制转换为ParameterizedType(第3行)。关键在于Java确定此Type对象时,它将使用子级中存在的类型信息将类型信息与新ParameterizedType实例中的类型参数相关联。因此,现在我们可以访问泛型的具体类型。
  • 第6行按类代码中声明的顺序获取映射到我们的泛型中的类型数组。对于此示例,我们提取第一个参数。这作为类型返回。
  • 第2行将最终的Type强制转换为Class。这是安全的,因为我们知道我们的泛型类型参数可以采用什么类型,并且可以确认它们都是类(我不确定在Java中如何获取不具有Class实例的泛型参数。实际上与此相关)。

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

如果您的具体孩子离任意跳数很远,或者您是具体的而不是最终的,这将变得很棘手,如果您希望任何(可变深度的)孩子都有自己的泛型,那就特别棘手。但是您通常可以围绕这些考虑因素进行设计,因此这可以助您一臂之力。

希望这对某人有所帮助!我知道这个帖子很古老。我可能会剪裁这个解释,并保留其他问题。


3
只需回答“我不确定在Java中如何获取一个没有关联到Class实例的泛型参数,实际上)”即可:如果泛型类的参数为“通配符表达式”(例如:?扩展SomeClass)或“类型变量”(例如:<T>,其中T来自通用子类)。在这两种情况下,返回的“ Type”实例都不能转换为“ Class”,因为每个VM可能具有不同的实现,这两种实现都实现了Type接口,但并不直接从java.lang.Class派生。
罗伯托·安德拉德

19

其实,我有这个工作。考虑以下代码段:

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)"

     }
 }

我正在使用jdk 1.6


12
-1:这不能解决问题中的问题;它只为您提供方法签名中声明的类型,而不是实际的运行时类型。
Michael Borgwardt

5
可能无法回答OP的问题,但这是一种有用的技术。
dnault

3
这是一个完全不同的问题的答案。
达伍德·伊本·卡里姆

您可以调用m.getParameterTypes()以获取所有实际类型。对于示例,它将返回“ List”。
Lee Meador

如果genericParameterTypes [i]不是ParameterizedType的实例,则它可能是一个Class。这意味着该方法的特定参数根本没有参数化。
Lee Meador

18

实际上,有一个解决方案,通过应用“匿名类”技巧Super Type Tokens的想法:

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>()。使用匿名类(如果可能的话)确保编译器保留有关SpiderMan赋予type参数的type参数的信息List<?>。瞧!


这间接地回答了这个问题:原始问题没有解决方案。为了使类型保留其通用参数,需要将其嵌入到另一个类型中,例如子类。本示例说明了如何更改调用代码,以便可以恢复chill()中的泛型类型参数。
Florian F

仅供参考,第一个链接是死的
Ashvin夏尔马

@AshvinSharma我相信,同样的材料,请访问:rgomes.info/using-typetokens-to-retrieve-generic-parameters
晏-盖尔Guéhéneuc

7

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

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) {

    }

}

7

@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
}

这不是原始帖子所要求的。在这里,您创建了的子类Awesomeable<Beer>。在那种情况下,类型信息被保留。如果您通过new Awesomable<Beer> ()该方法,wt将无法工作。
Florian F

@FlorianF如果您在Awesomable<Beer>没有这种具体子类的显式定义的EstrellaGalicia情况下即时进行传递,它仍然会退出参数化类型:我现在运行它:System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> 类型为:ParameterizedStuff $ Beer
Campa

6

不,那是不可能的。由于向下兼容性问题,Java的泛型基于类型Erase,例如在运行时,您拥有的只是一个非泛型List对象。在运行时有一些关于类型参数的信息,但是它存在于类定义中(即,您可以问“ 此字段的定义使用哪种通用类型? ”),而不是对象实例中。


尽管java可以在运行时将其存储为元数据,不是吗?我希望会。厄运。
cimnine

1
@ user99475:不。我是对的,安德烈是错的。他指的是我在回答的第二部分中提到的类型信息,但这不是问题所要的。
Michael Borgwardt

5

正如@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或多或少会这样做



1

您可以通过反射来获得泛型参数的类型,例如在此示例中(我在这里找到):

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());
    }
}

您将如何做同样的事情才能获得Home <K,V>的V?
拉斐尔·桑切斯

1

由于Java通用类型擦除,对问题的快速回答是不可以。

更长的答案是,如果您创建了这样的列表:

new ArrayList<SpideMan>(){}

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

我不建议您使用列表来执行此操作,但这是一个侦听器实现:

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

而且,由于推断超级类和超级接口的通用类型在JVM之间发生变化,因此通用解决方案并不像某些答案所建议的那样直接。

现在是我做的。


0

这是不可能的,因为Java的泛型仅在编译时考虑。因此,Java泛型只是某种预处理器。但是,您可以获取列表成员的实际类别。


是的,这就是我现在正在做的。但是现在我想知道类型,即使列表为空。但是四个家伙不会错。谢谢你们)!
cimnine

19
这不是真的。虽然很棘手,但您可以在下一篇文章中看到ParameterizedType可以做到这一点。
Edmondo1984

1
同意,可以做到。DerMike的示例概述了它(对我有用)。
mac

大声笑“四个家伙不会错”。这个答案是正确的。
达伍德·伊本·卡里姆

0

我已经为希望接受或返回的方法进行了编码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());
}

0

您无法从变量获取通用参数。但是您可以从方法或字段声明:

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

这在线程“主”中给我一个异常java.lang.ClassCastException:sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Lluis Martinez

“ params”是具有多个子接口的“ Type”。TypeVariableImpl是用于方法参数的参数,它告诉<>内的泛型名称,而不是其表示的Class。
Lee Meador

0

就我而言,阅读这段代码非常困难,我将其分为两行:

// 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;
}

0

这是另一个技巧。使用通用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<> ();
    }
}

0

我注意到许多人倾向于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.persistentClassClass<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;
  }
}

-1

用:

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

错了 toArray()返回Object[]。您不能也不应依赖于它是某些特定的子类型。
Radiodef
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.