获取泛型类的泛型参数类型名称

42

在使用泛型时,我在Java中遇到了一个小问题。我有一个类A

public class A<T>

A方法中,我需要获取T的类型名称。是否有一种方法可以使用T查找字符串s
(如果我创建了A<String> temp = new A<String>();,我希望能够在某个时候获取java.lang.String - 我必须使用泛型,因为我的其中一个方法将返回List<T>)。
这似乎很容易,但我不知道如何做到这一点。

2
请参考此问题:https://dev59.com/vXA75IYBdhLWcg3wPmZ8 - Victor Sorokin
6个回答

62

由于类型擦除,通常情况下无法做到这一点 - A<String> 的实例不知道 T 的类型。如果您需要它,一种方法是使用类型字面量:

public class A<T>
{
    private final Class<T> clazz;

    public A<T>(Class<T> clazz)
    {
        this.clazz = clazz;
    }

    // Use clazz in here
}

然后:

A<String> x = new A<String>(String.class);

这很丑,但这就是类型擦除所造成的:(

另一种选择是使用像Guice的TypeLiteral这样的东西。这种方法可以工作是因为用于指定超类的类型参数不会被擦除。因此你可以这样做:

A<String> a = new A<String>() {};

a现在指向A<String>的一个子类,因此通过获取a.getClass().getSuperClass(),最终可以回到String。但这样做非常糟糕。


38

您可以从子类获取泛型的名称。请参见此示例。我们像这样定义一个父类:

public class GetTypeParent<T> {

    protected String getGenericName()
    {
        return ((Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0]).getTypeName();
    }
}
我们可以通过以下方式来定义它的子类:
public class GetTypeChild extends GetTypeParent<Integer> {
    public static void main(String[] args) {
        GetTypeChild getTypeChild = new GetTypeChild();
        System.out.println(getTypeChild.getGenericName());
    }
}

你可以看到在主方法或任何实例方法中,我都能获取泛型类型的名称,在这种情况下,主方法将打印:java.lang.Integer


1
...然后您将获得子类的名称(如果有)。很好! - Per Lindberg
1
这似乎确实很聪明...但是你必须“硬编码”类型String才能获得String返回。问题不是尝试找到通用参数的值吗? - mike rodent
我不认为这有帮助。子类已经知道类型,因为它是硬编码的:String。调用父类的方法获取它是没有用的。不幸的是,这不是一个有用的用例,并且正如@mikerodent所提到的那样,这也不是OP所询问的。 - Marco Altieri
1
如果您想在运行时知道类型,例如,如果您有一组从共同的超类继承的类,则可以使用它。 - Enrico Giurin
@mikerodent 不,getGenericName() 返回子类中 T 的真实类型,此处为 java.lang.Integer。我已更改示例,之前的 T 是 java.lang.String,可能会令人困惑。 - Enrico Giurin

15

简短回答:不可能。

略微详细的解答:一旦你的代码编译完成,类型参数就会被丢弃。因此,Java无法知道您设置了什么。但是,您可以将相关的类传递给对象并对其进行操作:

public class Example<T> {
  private final Class<T> clazz;

  public Example(Class<T> clazz){
     this.clazz = clazz;
  }
...
}

这一定是答案,肯定的。由于擦除(这是一个我经常听到但从未完全深入分析过的术语),您永远不能将泛型参数用作"夹带进来"的额外参数。相反,请使用常规参数。似乎奇怪的是,这些信息永远无法获得,我不清楚这是设计选择还是不可避免的... - mike rodent
私有的最终类是什么意思?clazz 没有类型。还有 10 个赞? - madhairsilence
1
@mikerodent 这并非完全正确。这里的答案在“不行”和“可以”之间来回摇摆。事实上,两者都是对的,具体取决于情况。你应该搜索关于具象化类型的资料。基本上,如果你做 class Foo implements List<Integer> ,那么你就可以得到泛型类型。但是,如果你像 List<Integer> foo; 这样做,由于类型擦除,你是无法得到泛型类型的。并非所有的泛型类型都被擦除了,它取决于你对泛型的使用方式。 - searchengine27
1
另外,针对您提到的另一个问题,这种情况发生的原因是一种设计选择...有点像。基本上,那些被擦除的通用类型之所以会被擦除,是因为它们只能在编译时由编译器进行验证,而在运行时根本无法进行验证,因此在字节码中包含它们是没有意义的。 - searchengine27
从英文翻译成中文。只返回翻译后的文本:答案复制自https://dev59.com/1Ww15IYBdhLWcg3wbLHU#6624140 - GoldBishop

4

1
非常喜欢Apache Commons,之前从未听说过TypeUtils,所以感谢您的提醒。但是目前在我的情况下它无法运行:A.class出现了错误,“类型参数A的非法类字面量”...我会继续探索/尝试使用TypeUtils... - mike rodent
@mike rodent,当我在继承结构中使用多个类和不同数量的类型时,这对我很有效。您是否有一个无法正常工作的示例可用? - Hazok
后来:不行,我做不到。不确定如何首先获取ParameterizedType。我也看了乔纳森的答案和恩里科·格林的答案。但是,即使使用Apache,直接从实例中获取通用类型(OP的问题)似乎也是不可能的。除非你能告诉我怎么做! - mike rodent

3
在Java中,泛型是通过擦拭实现的,因此你无法在运行时获取用于创建泛型集合的“类型”名称。另外,为什么不检查元素以了解它所属的类型呢?

实际上我不能这样做,因为一开始我没有任何T的实例。该对象实现了一个OSGi服务监听器,我想用我的接口名称过滤捆绑消息。只有在此之后我才能获取这些实例。 - ThR37

1
如果您正在子类中进行操作,并且其父类定义了通用类型,那么以下是我使用的方法:
// get generic type class name
String name = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].toString();

// then when you've got the name, you can make the Class<T> object
Class.forName(name.replace("class ", ""))

我不能使用#getClass()而不是#toString()的原因是,我总是得到“Class”类,这对我没有用。


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