Java泛型类 - 确定类型

51
如果我要创建一个通用的Java类,例如:
public class Foo<T>

如何在该类内部确定“T”的值是什么?
public ???? Bar()
{
    //if its type 1
    //    do this
    //if its type 2
    //    do this
    //if its type 3
    //    do this
    //if its type 4
    //    do this
}

我已经研究了Java API并尝试使用反射等功能,例如instanceof、getClass、.class等等,但是似乎无法理解。我感觉离成功很近,只需要结合多个调用,但总是失败。
更具体地说,我正在尝试确定该类是否使用了3种可能类型之一进行实例化。

15
这样做的愿望几乎肯定是一个糟糕的设计选择。很有可能if语句需要了解每个“类型”才能实现,因此您应该将所有这些类都设为相同的父类型,并且“这样做”应该是一个虚拟方法。这可能涉及包装类,但是如果您走这条路,整体上您的设计将会改善。 - Bill K
2
if语句中的可能性是所有可能的调用类型中的边缘情况,这些情况需要完全分开实现。我同意你在这里所说的,但具体应用却是进退两难。 - dreadwail
1
需要获取泛型类型参数的类并不总是一个糟糕的设计决策。一个合法的需求(我的! :))可能是需要一个Class<>参数的方法调用。 - dahvyd
我需要为通用DTO转换器做同样的事情。在我看来,这是一个非常合理的问题。 - Dave
8个回答

60

我在几个项目中使用了类似于他在这里解释的解决方案,发现它非常有用。

http://blog.xebia.com/2009/02/07/acessing-generic-types-at-runtime-in-java/

它的核心是使用以下内容:

 public Class returnedClass() {
     ParameterizedType parameterizedType = (ParameterizedType)getClass()
                                                 .getGenericSuperclass();
     return (Class) parameterizedType.getActualTypeArguments()[0];
}

13
这句话的意思是:程序出现了异常,错误信息为“Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType”,你确定这样做可行吗? - Ram
11
这是Android中发生的事情:getClass().getGenericSuperclass() 返回代表java.lang.Object的Class对象,该对象无法转换为ParameterizedType类型。如果您的类继承自虚拟的Base<T>类,则返回值表示"Base<T>",getActualTypeArguments()[0]返回一个名为"T"的Type类型,该类型甚至无法转换为Class类型。这样行不通。 - misiu_mp
4
值得注意的是,这只适用于在Java代码中明确声明了类型参数的类。这种逻辑无法适用于通过动态代理实现的模拟对象等情况。 - Paul Morie
那么我如何检查返回的类是什么类型呢?例如:if (returnedClass isa Double).... if (returnedClass isa Integer)... - dwjohnston
这是否意味着类的泛型信息与永久代中的类信息一起存储? - user253202
显示剩余8条评论

44

与.NET不同,Java的泛型是通过一种叫做“类型擦除”的技术实现的。

这意味着编译器在生成类文件时会使用类型信息,但不会将此信息传递到字节码中。如果你用javap或类似的工具查看编译后的类,你会发现List<String>在类文件中只是一个简单的List(由Object组成),就像在Java 5之前的代码中一样。

访问泛型List的代码将被编译器“重写”,以包括您在早期版本中必须自己编写的转换语句。事实上,以下两个代码片段在编译器完成后从字节码的角度来看是相同的:

Java 5:

List<String> stringList = new ArrayList<String>();
stringList.add("Hello World");
String hw = stringList.get(0);

Java 1.4及以前版本:

List stringList = new ArrayList();
stringList.add("Hello World");
String hw = (String)stringList.get(0);
在Java 5中,从泛型类读取值时会自动插入到声明的类型参数所需的强制转换。在插入时,编译器将检查您尝试放置的值,如果不是字符串则会中止并出现错误。
整个过程的目的是为了使旧库和新的泛型代码相互操作而无需重新编译现有库。这是Java相对于.NET方式的主要优势,其中泛型类和非泛型类并存但不能自由交换。
两种方法各有利弊,但这就是Java的方式。
回到您最初的问题:由于编译器完成其工作后,类型信息将不再存在,因此您将无法在运行时获取类型信息。这在某些方面肯定是有限制的,并且有一些古怪的解决方法通常基于在某个地方存储类实例,但这不是标准功能。

13

由于 类型擦除 的原因,直接进行此操作是不可能的。但是,你可以将一个 Class<T> 传递到构造函数中并在你的类内部保存它。然后你可以将其与三种可能的 Class 类型进行比较。

然而,如果只有三种可能的类型,你可能需要考虑重构为 枚举


这似乎是最好的解决方案,虽然不如真正的通用类那么漂亮。感谢您的建议。 - dreadwail
再次强烈建议您考虑多态性,而不是基于类型的 if 语句。 - Michael Myers
很遗憾,考虑的项目没有任何关联,因此多态解决方案不实用。可能会落入if语句逻辑的项是所有其他“正常”处理的边缘情况,必须完全以不相关的方式进行处理。 - dreadwail
1
@dxmio:这些项目之间的关系是:你正在创建它们的 Foo<>。我并不是在说这样很轻浮:你可以创建一个“Fooable”接口,让每个项目实现它 - 并且该接口可以有一个返回它们类型(或更好的,枚举)的单个方法。 - Carl Manaster
这在Java <= 1.6.0.32中失败,出现了不可比类型错误。 - Dave
你能给一个具体的例子吗?我遇到的问题是在我传递给方法的类中无法实例化同类型的新类... - Vincent Gerris

3
问题是在编译过程中,大多数通用内容都会消失。
一种常见的解决方案是在创建对象时保存类型。
有关 Java 类型擦除行为的简要介绍,请阅读此页面

1
这非常有帮助 - 我看到了一些涉及此的解决方案,可能最终会采用这种方法。 - dreadwail
1
可能意味着你的设计不太好。 - Tom Hawtin - tackline

1
如果您知道一些有意义的特定类型,应该创建您通用类型的子类来实现它们。
因此,
public class Foo<T>

public ???? Bar()
{
    //else condition goes here
}

然后

public class DateFoo extends Foo<Date>

public ???? Bar()
{
    //Whatever you would have put in if(T == Date) would go here.
}

0

泛型类的整个意义在于您不需要知道正在使用的类型...


2
足够正确,但是如果我设计一个可以通用实例化为任何类型的类,我想要处理特定子集的所有可能类型作为具有特定逻辑的边缘情况。 - dreadwail
然后创建不同的子类型。或委托给策略(或类似)处理。 - Tom Hawtin - tackline

0

看起来你想要的实际上并不是一个通用类,而是一个具有许多不同实现的接口。但如果你阐述一下你的实际、具体目标,也许会更清晰明了。


0

我同意Visage的观点。泛型是用于编译时验证,而不是运行时动态类型。听起来你需要的实际上只是工厂模式。但如果你的“做这个”不是实例化,那么一个简单的枚举可能同样有效。就像Michael所说的,如果你有一个稍微具体一些的例子,你会得到更好的答案。


我必须在这里表示不同意,而且已经有几次提到在特定的问题空间中需要一个非多态解决方案,我对收到的答案感到非常满意。 - dreadwail

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