【问题标题】:How to get the Generic Type Parameter?如何获取泛型类型参数?
【发布时间】:2013-10-26 10:39:02
【问题描述】:

简单地说:

public static class MyClass<T> {
    // i don't want to keep an instance of T, if it is not necessary.
    // and it is not nice, not neat.

    // Or, let's say, the member are in the form of :
    ArrayList<T> mArrayList = new ArrayList<T>();
    // the problem of getting the generic type parameter is still present.
}

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>();
    getParamType( myObject );
}

private static final <T> void getParamType(final MyClass<T> _myObject) {
    System.out.println(_myObject.getClass().getTypeParameters()[0]);    // T
    System.out.println(((T) new Object()).getClass());                  // class java.lang.Object
}

如何让代码打印class java.lang.Integer?

我知道很多 stackoverflow 线程都在询问(并回答)这个问题。然而他们无法解决这个问题。

  • 我不知道为什么有些人需要调用getGenericSuperclass() - 因为在这个简单的案例中不涉及继承。
  • 我也无法将其转换为 ParameterizedType。

.

System.out.println((ParameterizedType) _myObject.getClass());
// Compile Error: Cannot cast from Class<capture#11-of ? extends TreeTest2.MyClass> to ParameterizedType

System.out.println((ParameterizedType) _myObject.getClass().getGenericSuperclass());
// Runtime Exception: java.lang.ClassCastException

根据@Thomas 的指南,我找到了一种解决方法 方法来获取class java.lang.Integer。

首先,我们在测试代码中创建MyClass&lt;T&gt; 的一个匿名(它必须是匿名的)子类。 (这很奇怪。为什么它只支持子类?)

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>() {};  // Anonymous sub-class
    getParamType( myObject );
}

然后我们可以使用getGenericSuperclass() 方法得到Type,然后将其转换为ParameterizedType,然后使用getActualTypeArguments():

private static final <T> void getParamType(final MyClass<T> _myObject) {
    System.out.println( ((ParameterizedType) _myObject.getClass().getGenericSuperclass()).getActualTypeArguments()[0] );
}

完美打印class java.lang.Integer。

这是不太好,因为测试代码应该模拟实际情况,用户很可能不会继续创建无意义的子类。

这种方法是基于the TypeReference class 的思想。但我真的不知道如何使用它。我试过class MyClass&lt;T&gt; extends TypeReference&lt;T&gt;。但是我仍然必须创建MyClass&lt;T&gt; 的子类才能让TypeReference.getType() 打印class java.lang.Integer。

请提供帮助,感谢您提供的任何意见,因为目前还没有最好的方法。


基于上述解决方法的另一个问题:为什么只有匿名子类有效?

public static class SubMyClass<T> extends MyClass<T>{}

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>() {};  // Anonymous sub-class
    getParamType( myObject );               // class java.lang.Integer

    MyClass<Integer> mySubObject = new SubMyClass<Integer>();   // named sub-class
    getParamType( mySubObject );            // T
}

(MyClass 和 getParamType() 不变。)

【问题讨论】:

    标签: java generics reflection generic-programming type-parameter


    【解决方案1】:

    这有点困难,因为 Java 故意不能做到这一点(“类型擦除”)。

    解决方法称为super type tokens。 SO 上也有一些关于此的主题(如this one 或that one)。

    【讨论】:

    • 谢谢托马斯! TypeReference 类对我来说是新的。我能解释为什么TypeReference&lt;T&gt; 可以得到它的T,因为(1)它是抽象的,因此强制继承,(2)只有Type java.lang.Class.getGenericSuperclass() 可以转换为ParameterizedType,因此我们可以使用getActualTypeArguments?
    • 如果有像 Type java.lang.Class.getGeneric_____class() 这样的方法,我们可以在其中执行 ((ParameterizedType) getClass().getGeneric____class()).getActualTypeArguments() 会很棒,不是吗?顺便说一句,为什么 Java 只有返回 Type 的“get super”方法?
    • 回到原来的问题。如何在TypeReference的帮助下获得class java.lang.Integer?通过new TypeReference&lt;List&lt;String&gt;&gt;() {}.getType() 我可以得到java.util.List&lt;java.lang.String&gt;(但这没有用,因为它不是从对象中获取的)。然后我让class MyClass&lt;T&gt; extends TypeReference&lt;T&gt; 和myObject.getType() 我只得到T。谢谢你指引我的路。请帮助我更进一步。
    【解决方案2】:

    当你有这样的问题时,你应该问问自己,如果没有泛型,你会怎么做?因为任何泛型程序都可以在没有泛型的情况下转换为等效程序。 (这种转换称为类型擦除。)因此,如果没有泛型就无法做到这一点,那么使用泛型也无法做到。

    没有泛型的程序如下所示:

    public static class MyClass {
        ArrayList mArrayList = new ArrayList();
    }
    
    @Test
    public final void test() {
        MyClass myObject = new MyClass();
        Integer result = getParamType( myObject ); // how would you implement getParamType()?
    }
    

    【讨论】:

    • 如果没有泛型,如何获取泛型?
    • @midnite:这与泛型无关。这是关于程序的逻辑。例如,如果你让MyClass 的构造函数接受一个代表类的Class&lt;T&gt; 类型的参数,并将它存储在类中,那么在擦除之后,你会看到你正在做MyClass myObject = new MyClass(clazz);,并且您在类中存储了一个Class 类型的变量。从这里你可以清楚地看到为什么它有效而你的方法无效。
    • 感谢您的回复。保持Class&lt;T&gt; 作为成员字段,我理解它为什么有效。但我不明白相反的情况。就像,有了成员字段public T mField;,之后我们也可以从mField.getClass() 中检索T。
    • 而且,为什么只有getGenericSuperclass() 而没有getGenericThisclass()?这似乎不合逻辑。
    • @midnite:泛型只是编译时类型检查的东西。这是一种错觉。它并不真正“存在”。您要求输入类型参数,因为您在这种错觉中看到了它。当你剥离幻觉时,我向你展示了它是什么,向你展示你所看到的东西并不存在,你需要别的东西。要记住的最重要的事情是:当您将泛型添加到一段代码时,它永远不会赋予您执行在泛型之前无法完成的事情的神奇能力。这就是为什么问问自己如果没有泛型你将如何做到这一点是很有启发性的。
    【解决方案3】:

    Java 有一个名为 Type Erasure 的误导性功能,专门阻止您这样做。

    运行时不存在通用参数信息。

    相反,您可以手动接受Class&lt;T&gt;。

    【讨论】:

    • 感谢您的回复。 “手动接受 Class”是什么意思?
    • 在构造函数中添加Class&lt;T&gt; 参数。
    • 擦除是一种妥协。我不会称之为误导。
    • 是不是这样,我们在创建对象时必须调用MyClass&lt;Integer&gt; myObject = new MyClass&lt;Integer&gt;(Integer.class);?这很危险,因为我们可以调用MyClass&lt;Integer&gt; myObject = new MyClass&lt;Integer&gt;(String.class);,这也是有效的。
    • @midnite 如果您将参数声明为Class&lt;T&gt;,则不会。
    【解决方案4】:

    要了解 T 的值,您需要通过继承 MyClass 在类型定义中捕获它:

    class MyStringClass extends MyClass<String> {}
    

    如果你愿意,你也可以使用匿名类来做到这一点:

    MyClass<String> myStringClass = new MyClass<String>{}();
    

    要解析T的值,可以使用TypeTools:

    Class<?> stringType = TypeResolver.resolveRawArgument(MyClass.class, myStringClass.getClass());
    assert stringType == String.class;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-15
      • 1970-01-01
      • 2012-09-21
      • 1970-01-01
      • 2021-07-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多