非泛型引用泛型类导致非泛型返回类型。

8

我有一个遗留类,该类本身不是泛型,但其中一个方法的返回类型使用了泛型:

public class Thing {
    public Collection<String> getStuff() { ... }
}

getStuff()使用泛型来返回一个字符串集合。因此,我可以遍历getStuff(),而不需要将元素强制转换为String

Thing t = new Thing();
for (String s: t.getStuff())  // valid
{ ... }

然而,如果我将 Thing 本身更改为通用类型但保持其他所有内容不变:
public class Thing<T> {
    public Collection<String> getStuff() { ... }
}

然后继续使用非泛型引用ThinggetStuff()不再返回Collection<String>,而是返回一个非类型化的Collection。因此客户端代码无法编译:

Thing t = new Thing();
for (String s: t.getStuff())  // compiler complains that Object can't be cast to String
{ ... }

为什么会这样?有什么解决方法吗?

我猜测是因为使用非通用引用来引用通用类,Java会关闭整个类的所有泛型。这很麻烦,因为现在我通过将Thing变成通用类而破坏了客户端代码。

编辑:我正在为另一个未在上面示例代码中列出的方法使Thing变成通用类。我的问题是教育性质的,即为什么上述操作不可行。

3个回答

10

好的,第二次尝试,我误解了你的问题。

当你声明Thing(这被称为原始类型)而不是Thing<?>参数化类型)时,Java编译器会剥离所有泛型参数,即使(在你的情况下)方法的泛型类型与类的泛型类型无关。

来自(优秀的)Java Generics FAQ

我可以像使用任何其他类型一样使用原始类型吗?

原始类型的方法或构造函数具有它们在类型擦除后所具有的签名。

这个看似无害和不引人注目的句子描述了这个行为。你正在使用Thing作为原始类型,因此返回类型是Collection(而不是Collection<String>),因为这是类型擦除后的类型。

感到困惑?不足为奇。看看那个FAQ的大小。可能只有三个人了解Java泛型的全部含义。考虑一下我最喜欢的JDK声明:

Enum<T extends Enum<T>> 

(FAQ 中也有关于这个的解释。)

我将Thing泛型化,因为有另一个方法(没有在我的示例中列出)。 - Steve Kuo
如果thing是Thing(而不是Thing<?>),它将无法编译。试试看吧。 - Steve Kuo
误解了问题,修改过了。 - cletus
1
@Jon:这一切源于保持向后兼容性(类型擦除)的决定。Java开发人员发现C#中的IEnumerable<T>和IEnumerable彼此之间没有关系有些奇怪。 - cletus

0

它失败是因为擦除。您可以在这些Java教程中了解更多信息。


0

我认为这是非常正常的。在我看来,对于启用泛型的类使用Thing t = new Thing();是一个错误。当编译器看到一个泛型类被用作没有类型参数的类时,它会认为必须从该类中擦除所有泛型类型。这就是你可以在新的Java编译器中编译旧代码而不使用泛型,并且编译器让那些旧代码使用启用泛型的类(例如java.util.ArrayList)而没有任何问题的原因。(这也是Java不需要像C#一样分开System.Collection.Generic.List和System.Collection.List的原因)。你可以运行Thing t = new Thing();并在其上添加一个简单的类型参数Thing<Object> t = new Thing<Object>();,Java编译器唯一需要做的就是确保你有意识地使用Java泛型。我永远不会因为Java的很好的向后兼容性而责怪它。

我知道我有点晚了:D


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