Java:有界类型的getClass()

时间:2013-08-09 10:22:57

标签: java type-erasure type-parameter

当我在使用仿制药时,我发现了一些东西。在下面的示例中,doStuff1编译但doStuff2不编译:

public <T extends Foo> void doStuff1(T value) {
    Class<? extends Foo> theClass = value.getClass();
}

public <T extends Foo> void doStuff2(T value) {
    Class<? extends T> theClass = value.getClass();
}

所以,我查看了Object.getClass()的文档,发现了这个:

  

实际结果类型是Class&lt;? extends | X |&gt;其中| X |是擦除调用getClass的表达式的静态类型。

这让我有点好奇。为什么getClass()以这种方式设计?如果适用的话,我可以理解将类型转换为它们的原始类,但我认为没有明显的理由说明为什么它们必须使它同时杀掉T。有没有一个特定的原因,它也可以摆脱它,或者它只是一个普遍的“让我们摆脱一切因为它更容易;谁还需要它”呢?

5 个答案:

答案 0 :(得分:9)

如果getClass()返回Class<? extends X>,则不会发生任何真正的错误;实际上它会帮助很多用例。

唯一的问题是,它在理论上是不正确的。如果某个对象是ArrayList<String>,则其class不能是Class<ArrayList<String>> - 没有这样的类,只有Class<ArrayList>

这实际上与擦除无关。如果有一天Java获得完整的具体类型,getClass()仍应返回Class<? extends |X|>;但是应该有一个新的方法,比如getType(),它可以返回更详细的Type<? extends X>。 (但是,getType可能与许多现有的类使用自己的getType方法冲突)

对于时间安排,由于Class<? extends X>在很多情况下可能有用,我们可以设计自己的方法来做到这一点

static <X> Class<? extends X> myGetClass(X x){ ... }

但是可以理解他们不会在标准库中使用这种黑客。

答案 1 :(得分:0)

请考虑以下程序:

var a = new ArrayList<String>();
var b = new ArrayList<Integer>();
var aType = a.getClass();        
var bType = b.getClass();        

if (aType == bType) {
  ...
}

如果执行此操作,aTypebType将包含相同的运行时类对象(因为通用类型的所有实例共享相同的运行时类),并且if语句的主体将执行

它也可以编译。特别是,aTypebType的声明类型都是Class<? extends ArrayList>,比较两个兼容类型的引用对编译器是有意义的。

但是,如果将getClass定义为返回Class<? extends T>,则aType的编译时间类型将为Class<? extends ArrayList<String>,而bType的类型将为是Class<? extends ArrayList<Integer>>。由于Java将泛型定义为不变型,因此它们是不可转换的类型,因此即使运行时认为比较正确,也要求编译器拒绝比较aType == bType毫无意义。

由于要解决此问题,因此需要对Java类型系统进行重大扩展,因此声明getClass返回擦除可能被视为是更简单的选项,即使它在其他情况下也会引起违反直觉的行为,例如您遇到的那个。当然,以这种方式定义后,就不能在不破坏API的情况下对其进行更改...

要变通解决此问题,您可以使用未经检查的演员表:

@SuppressWarnings("unchecked")
<T> Class<? extends T> getTypedClassOf(T t) {
   return (Class) t.getClass();
}

当然,这意味着您可能需要诉诸未经检查的强制转换,以使编译器了解不可转换类型的类对象毕竟可能是相同的...

答案 2 :(得分:-1)

您无法确定value实际上是否为T,它也可能是T的子类。getClass()会返回value的“真实”类,但您可以'确保是T,因此必须阅读Class<? extends T>!这与T是类型参数这一事实无关。

[编辑] 好吧不太,我没有意识到method2没有编译。我认为这是一个编译器问题。我得到了

Type mismatch: cannot convert from Class<capture#1-of ? extends Foo> to Class<? extends T>

我认为编译器只会意识到T对象是Foo。编译器应该知道getClass()会返回一些Class<? extends T>,但似乎只知道它会返回一些Class<? extends Foo>。这是否意味着Foo是T的 erasure of the static type

答案 3 :(得分:-1)

非常好的问题。

java泛型的整个问题是它们在旧虚拟机中不存在。 Sun决定添加通用支持,而现有虚拟机不支持它们。

这可能是一个大问题,因为当他们发布新的Java时,旧的虚拟机将无法运行为最新版本的Java编写的任何代码,并且他们将无法继续支持旧虚拟机等。

因此,他们决定以可以在旧虚拟机中运行的方式实现泛型。 他们决定从BYTECODE中删除他们。

所以整个故事都是关于jvms的支持。

编辑:但这可能与这个问题无关。请参阅kutschkem的回答!

另外,我的猜测是,在第二种情况下,演员表是Class<? extends List>Class<? extends T>。 只有当List直接扩展T(因为它是隐式转换)时才能进行此转换。但另一方面,T扩展了List。

我认为与说法相同:

String s = "s";
Object o = s;

在上面的例子中,可以进行强制转换,因为String扩展了Object。对于反向,您需要一个显式的强制转换。

如果您向程序添加显式强制转换,则会编译:

public <T extends List> void doStuff2(T value) {
   Class<? extends T> theClass = (Class<? extends T>) value.getClass();
}

答案 4 :(得分:-2)

guava reflection工具提供了一些工具来解决这个限制,并帮助您获得有界类型的类型(所以类)。

如本说明页第一行所述:

  

由于类型擦除,您无法传递泛型类对象   运行时 - 你可以抛出它们并假装它们是通用的,   但他们确实不是。

     

Guava提供TypeToken,它使用基于反射的技巧来允许   即使在运行时也可以操作和查询泛型类型。

想法是获得(或创建)有界类型的TypeToken,然后你可以询问它的类型。

我认为这个答案有点偏离主题,因为它没有直接回答问题('为什么'部分),但它可以修复不可编译的方法,它可以导致代码能够解决问题并给你解答为什么部分,并肯定会帮助其他人寻找'Java: - 如何-getClass()的有界类型“