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

cim*_*ine 128 java generics reflection

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

一个例子:

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>());
    }
}
Run Code Online (Sandbox Code Playgroud)

Der*_*ike 149

一个构造,我曾经偶然发现,看起来像

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
Run Code Online (Sandbox Code Playgroud)

所以似乎有一些反思 - 魔术,我不幸完全不理解......抱歉.

  • 尽管由于某些原因,这已经很老了*并且被接受了*,但由于它根本没有回答这个问题,所以我已经投票了.它不会*在问题中给出的例子中给出"蜘蛛侠".它在某些情况下无疑是有用的,但它对于被问到的问题不起作用. (37认同)
  • 到目前为止,没有人会接受"无法做到"的答案.我们在这里是因为我们绝望了. (14认同)
  • 我得到这个异常:`线程中的异常"main"java.lang.ClassCastException:java.lang.Class无法强制转换为java.lang.reflect.ParameterizedType`,不确定约束是什么. (12认同)
  • 就像@Dish我只得到一个`java.lang.ClassCastException:java.lang.Class不能转换为java.lang.reflect.ParameterizedType` (3认同)
  • 但是宣布它是抽象的.当你想使用它时,然后将其内联子类. (2认同)

Mar*_*nna 127

我想试着打破@DerMike的答案来解释:

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

其次,这为通用类提供了通用类型信息,与通用类型参数的层次结构完全相同 - 即具有泛型类型参数的抽象父类可以找到与其类型参数对应的具体类型,以便具体实现自身即直接从它继承.如果这个类是非抽象的并且被实例化,或者具体的实现是两个级别,那么这将不起作用(虽然一点点jimmying可以使它适用于超过一个或最低级别的任何预定数量的级别使用X泛型类型参数,等等.

无论如何,关于解释.这里是代码,分为行以便于参考:

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

让'us'成为包含此代码的泛型类型的抽象类.从内到外阅读:

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

......那就是它.因此,我们将类型信息从我们自己的具体实现推送回自己,并使用它来访问类句柄.我们可以加倍getGenericSuperclass()并进入两个级别,或者消除getGenericSuperclass()并为自己获取值作为具体类型(警告:我还没有测试过这些场景,它们还没有找到我).

如果你的具体孩子是一个任意数量的跳跃,或者你是具体的而不是最终的,如果你期望你的(可变深度)孩子有自己的仿制品,那就太麻烦了.但是你通常可以围绕这些考虑因素进行设计,因此这可以帮助你完成大部分工作.

希望这有助于某人!我认识到这篇文章很古老.我可能会剪断这个解释并保留其他问题.

  • 只是回答你的"我不确定如何在Java中获取一个没有与它关联的Class实例的泛型参数,实际上"":如果泛型类的参数是"通配符表达式"(例如:?extends SomeClass)或"类型变量"(例如:<T>,其中T来自通用子类).在这两种情况下,返回的"Type"实例都不能转换为"Class",因为每个VM可能对实现Type接口的两个实现具有不同的实现,但不直接从java.lang.Class派生. (3认同)

And*_*nov 20

实际上我让这个工作.请考虑以下代码段:

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

     }
 }
Run Code Online (Sandbox Code Playgroud)

我正在使用jdk 1.6

  • -1:这不能解决问题中的问题; 它只提供方法签名中声明的类型,而不是实际的运行时类型. (10认同)
  • 可能不会回答OP的问题,但它是一种有用的技术. (5认同)
  • 这是一个完全不同的问题的答案. (2认同)

Yan*_*euc 16

实际上有一个解决方案,通过应用"匿名类"技巧超级类型标记的想法:

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 {
}
Run Code Online (Sandbox Code Playgroud)

诀窍在于创建一个匿名类,new ArrayList<SpiderMan>() {}代替原始(简单)new ArrayList<SpiderMan>().使用anoymous类(如果可能)可确保编译器保留有关type参数SpiderMan的类型参数的信息List<?>.沃利亚!


Jar*_*ell 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) {

    }

}
Run Code Online (Sandbox Code Playgroud)


Mic*_*rdt 6

不,这是不可能的.由于向下兼容性问题,Java的泛型基于类型擦除,即在运行时,您拥有的只是非泛型List对象.在运行时有一些关于类型参数的信息,但它存在于类定义中(即,您可以询问" 该字段的定义使用哪种泛型类型? "),而不是在对象实例中.


Cam*_*mpa 6

@ 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
}
Run Code Online (Sandbox Code Playgroud)


Sni*_*las 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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

当你必须将一个类变量传递给ActivityInstrumentationTestCase2的构造函数时,这或多或少都是Google所做的.