繁体   English   中英

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

[英]Get type of a generic parameter in Java with 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>());
    }
}

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

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

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

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

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

其次,这为泛型类提供了与被检查的具体类型正好上一级层次结构的泛型类型信息——即具有泛型类型参数的抽象父类可以找到与其类型参数相对应的具体类型,以实现自身的具体实现直接从它继承。 如果这个类是非抽象的和实例化的,或者具体的实现低了两级,这将不起作用(尽管一点点的 jimming 可以使它适用于超过一个的任何预定数量的级别,或者直到最低级带有 X 泛型类型参数等)。

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

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

让“我们”成为包含此代码的具有泛型类型的抽象类。 大致从里到外阅读:

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

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

如果您的具体子代距离任意数量的跃点,或者如果您是具体的而不是最终的,这将变得棘手,如果您期望您的任何(不同深度的)子代拥有自己的泛型,则尤其棘手。 但是您通常可以围绕这些考虑因素进行设计,因此这可以帮助您完成大部分工作。

希望这对某人有所帮助! 我承认这篇文章很古老。 我可能会剪掉这个解释并将其保留用于其他问题。

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

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

实际上有一个解决方案,通过应用“匿名类”技巧和来自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信息,该类型参数提供给类型参数List<?> 瞧!

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

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

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

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

    }

}

正如@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 所做的。

不,这是不可能的。

你可以得到一个字段的泛型类型,因为类是该规则的唯一例外,即使这有点小技巧。

有关示例,请参阅了解 Java 中的泛型类型

您可以使用反射获取泛型参数的类型,例如我在此处找到的示例:

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

由于 Java 泛型类型擦除,问题的快速回答是否定的。

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

new ArrayList<SpideMan>(){}

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

并不是我建议用列表来做这件事,但它是一个监听器实现:

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

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

现在我做到了。

这是另一个技巧。 使用通用可变参数数组

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

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

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

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

就我而言,阅读这段代码很困难,我只是将它分成了 2 行可读的行:

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

我已经为期望接受或返回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());
}

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

使用:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM