17

当我在研究仿制药时,我注意到了一些事情。在下面的示例中,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<? 扩展 |X|> 其中 |X| 是调用 getClass 的表达式的静态类型的擦除。

这让我有点好奇。为什么要getClass()这样设计?如果适用的话,我可以理解将类型转换为它们的原始类,但我看不出为什么他们必须让它也被杀死的明显原因T。是否有特定的原因让它也摆脱它,或者它只是一般的“让我们摆脱一切,因为它更容易;无论如何谁会需要它”的方法?

4

5 回答 5

12

如果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){ ... }

但可以理解的是,他们不会将这种 hack 放入标准库中。

于 2013-08-09T15:08:51.833 回答
2

考虑以下程序:

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

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

如果我们执行这个,aType并且bType将包含相同的运行时类对象(因为泛型类型的所有实例共享相同的运行时类),并且 if 语句的主体将执行。

它也可以编译得很好。特别是aTypebTypeare both的声明类型Class<? extends ArrayList>,以及比较兼容类型的两个引用对编译器来说是有意义的。

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

由于修复此问题需要对 java 类型系统进行重大扩展,因此声明getClass返回擦除可能被视为更简单的选择,即使它在其他情况下会导致违反直觉的行为,例如您遇到的情况。当然,一旦以这种方式定义,它就无法在不破坏 API 的情况下进行更改......

要解决此变通办法,您可以使用未经检查的强制转换:

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

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

于 2020-11-26T10:18:32.357 回答
-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()返回 someClass<? extends T>但似乎只知道它返回 some Class<? extends Foo>。这是否意味着 Foo 是erasure of the static typeT 的?

于 2013-08-09T10:27:46.343 回答
-1

非常好的问题。

java泛型的全部问题在于它们在旧的虚拟机中不存在。Sun 决定在现有虚拟机不支持的情况下添加通用支持。

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

因此,他们决定以一种也可以在旧虚拟机中运行的方式实现泛型。他们决定从字节码中删除它们。

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

编辑:但这可能与问题无关。见库奇克姆的回答!

另外,我的猜测是,在第二种情况下,演员表是Class<? extends List>to 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();
}
于 2013-08-09T10:27:52.613 回答
-1

guava 反射工具提供了一些工具来解决这个限制并帮助您获取有界类型的类型(so class)。

如本说明页的第一行所述:

由于类型擦除,您不能在运行时传递泛型 Class 对象——您可以强制转换它们并假装它们是泛型的,但它们实际上不是。

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

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

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

于 2013-08-09T11:49:12.863 回答