Java 泛型: 在运行时访问泛型类型

15
我希望您能在运行时访问已声明字段的通用类型。之前我认为由于Java类型擦除而不可能实现,但是某些知名框架通过反射在运行时利用了泛型类型,因此这一点并非不可能实现。
例如,Guice将基于您提供的泛型类型实现一个提供程序:
public class Injectable{

    @Inject
    private Provider<SomeType> someTypeProvider;

}

如何通过反射API访问字段或任何类型/方法等的“SomeType”泛型属性?
此外,还需了解如何通过Java 6注释处理器API访问这些通用类型属性。
谢谢。
编辑:
感谢大家提供的指引。我通过haylem的链接找到了一种方法,特别是Prenkov的文章Java Reflection: Generics
以下是我正在寻找的答案:
/**
 * @author John Ericksen
 */
public class TypeReflectionExample {

    public class SomeType{}

    public class Injectable{
        @Inject  private Provider<SomeType> someTypeProvider;
    }

    public static void main(String[] args){

        try {
            Field providerField = Injectable.class.getDeclaredField("someTypeProvider");

            Type genericFieldType = providerField.getGenericType();

            if(genericFieldType instanceof ParameterizedType){
                ParameterizedType aType = (ParameterizedType) genericFieldType;
                Type[] fieldArgTypes = aType.getActualTypeArguments();
                for(Type fieldArgType : fieldArgTypes){
                    Class fieldArgClass = (Class) fieldArgType;
                    System.out.println("fieldArgClass = " + fieldArgClass);
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }   
}

结果如下:

fieldArgClass = 类 test.TypeReflectionExample$SomeType

同样的方法也可以用于方法、构造函数、超类扩展/实现等。

我授予haylem奖励,因为他的帖子引导我找到了这个解决方案,即使它并没有直接回答我的问题。


我应该将这个编辑移动到我的问题的答案中吗?正在尝试弄清楚应该授予答案给谁。 - John Ericksen
4个回答

15

在Java中,泛型由于采用了类型擦除的实现方式,通常在运行时无法得知具体类型。

反射泛型?

然而,根据Ian Roberston的文章Reflecting Generics和Prenkov的文章Java Reflection: Generics,您仍然可以提取有关声明类型(而不是运行时对象类型)的一些有价值的信息。

泛型和类型擦除的背景

泛型是在保持源代码和二进制兼容性的情况下引入的,因此它们存在一些限制,例如:

  • 没有至少一些指示泛型支持的简写形式是不可能的(这里,所谓的钻石操作符<>),
  • 无法在运行时检查泛型类型,因为它们必须使用类型擦除实现。

进一步阅读


1
好的,那么Guice是如何确定哪个Provider是适当的呢,就像我给出的例子一样? - John Ericksen
1
这个答案与问题有何关联?楼主并没有要求提供Java教程的链接。 - Varun Achar
@VarunAchar:我链接的所有页面都提供了有关泛型的背景,设计和实现以及不允许访问运行时对象类型的原因的说明。与其他不需要维护兼容性并且能够“正确”执行此操作的语言相反。 - haylem
我同意@VarunAchar的观点。对于这个问题,一个好的回答应该至少包含一些示例代码来执行(类似于)Guice所做的操作。(话虽如此,对我来说这并不值得被踩。) - millimoose
不,对我来说这是值得点赞的,因为它包含了很多有用的信息。 - Hovercraft Full Of Eels
显示剩余4条评论

4

好的,你为什么不看一下guice的做法?源代码是公开的。

Guice在多个级别上执行这些操作。其中一个特别突出的是type literals

关键点在于,虽然类型使用类型擦除进行编译(因此每种类型只有一个),但仍存在多个Type对象,它们知道使用的泛型。但是,该代码独立于此进行了优化(因为它是按类而不是按类型编译的)。

请查看ParameterizedType的Java API。

因此,尽管在类级别上Java泛型是通过“类型擦除”实现的,但在类型级别上并非完全如此。不幸的是,处理类型比处理类要棘手得多。此外,这也意味着Java无法像C++那样优化泛型,特别是对于基本类型。设计上,ArrayList<Integer>将成为包含对象的ArrayList<?>,而不是在可能的情况下支持原生int[]数组。
请注意,这与自己跟踪这些内容非常接近。比如,非常天真地(它不能处理嵌套泛型),您可以使用一个带有字段Class<T> contentClass的类扩展ArrayList<T>,然后您将能够在运行时找到这些信息。(TypeLiteral可能比Class更好的选择!)此外,JRE实际上不会确保列表保持一致性。就像您可以将ArrayList<Integer>强制转换为未类型化的ArrayList并添加一个String对象一样。

我接下来打算查看Guice源代码,只是想先在这里问问有经验的人。谢谢你们的指引。 - John Ericksen
3
不要将泛型和模板进行比较,这会让人误以为这两种语言特性有显著的共同之处。(就理解它们所需的心智模型而言,它们绝对没有任何共同点。) - millimoose
它们可能完全不同(而模板可以用于各种其他目的)。但是它们通常满足非常相似的需求,特别是对于类型专业化。感谢提醒。 - Has QUIT--Anony-Mousse

2

我大约一年前使用过这个。它可能会帮助到你。

Type typeOfSrc = type(YourClass.class, clazz);

// type(C, A1,...,An) => C<A1,...,An>
static ParameterizedType type(final Class raw, final Type... args)
{
    return new ParameterizedType()
    {
        public Type getRawType(){ return raw; }

        public Type[] getActualTypeArguments(){ return args; }

        public Type getOwnerType(){ return null; }
    };
}

这使您能够在运行时访问通用类型。基本上,您在这里做的是将通用信息存储在另一个类中,并在运行时使用该类来检索该信息。


0

我相信guice使用TypeLiteral来封装泛型信息在一个单独的对象中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接