问题 是否向后兼容用像Wild 这样的通配符替换像Collection这样的原始类型?


我是某个开源库的作者。其中一个公共接口有使用原始类型的方法 Collection, 例如:

public StringBuilder append(..., Collection value);

我明白了 Collection is a raw type. References to generic type Collection<E> should be parameterized 警告。

我正在考虑修复这些警告。实现实际上并不关心集合中元素的类型。所以我在考虑更换 Collection<?>

但是,这些方法是我的库的公共接口的一部分。客户端代码可以调用这些方法或提供这些公共接口的自己实现,从而实现这些方法。我担心会改变 Collection 至 Collection<?> 将打破客户端代码。所以这是我的问题。

如果我改变 Collection  - > Collection<?> 在我的公共接口中,这可能会导致:

  • 客户端代码中的编译错误?
  • 已编译的现有客户端代码中的运行时错误?

5851
2018-06-02 14:51


起源

可能重复 未绑定通配符与原始类型之间的差异 - Nikolas
@Nikolas我不认为这是重复的。 - lexicore
在14年前的JDK1.5中也添加了泛型。微软不支持一个充满漏洞和安全漏洞的12年历史的操作系统,同样你不应该支持具有相同问题的14岁编程语言。 - van dench
@vandench这不是支持一种14岁的编程语言。这是关于估计一个突破性变化将从多少转变 Collection 至 Collection<?> 是。仿制药的时代在很大程度上是无关紧要的。我之前没有更新公共API是我的错。坦率地说,它从来都不是优先事项。 - lexicore


答案:


在运行时进行此替换是不安全的。

我或许应该更确切地说,这种变化本身就是安全的;但它鼓励的后续变化可能导致失败。

a之间的区别 Collection 和a Collection<?> 是你可以向前者添加任何东西,而你不能向后者添加除文字null之外的任何东西。

因此,当前覆盖您的方法的人可能会执行以下操作:

@Override
public StringBuilder append(Collection value) {
  value.add(Integer.valueOf(1));
  return new StringBuilder();
}

(我不知道这个方法的用途是什么;这是一个病态的例子。它看起来就像是他们的东西 不能 做,但那与他们不一样  这样做)。

现在,假设这个方法被调用如下:

ArrayList c = new ArrayList();
thing.append(c);
c.get(0).toString();

(再次,我不知道它是如何用于真实的。请跟我一起)

如果你做了参数 append  Collection<?> 相反,也许令人惊讶的是(*),你不需要更新子类也是通用的: append 上面的方法将继续编译。

在基类中查看参数的新泛型类型,您可以认为现在可以使此调用代码非原始:

ArrayList<Double> c = new ArrayList<>();
thing.append(c);
c.get(0).toString();

现在,这里的问题是如何评估最后一行:那里有一个隐式演员。它实际上会被评估为:

Double d = (Double) c.get(0);
d.toString();

这是你可以调用的事实 toString() 在...上 Object:还有一个 checkcast 由编译器插入,以清除列表元素类型。这会在运行时失败,因为列表中的最后一项是整数,而不是 Double

关键是没有为原始类型版本插入强制转换。这将被评估为:

Object d = (Object) c.get(0);
d.toString();

这不会在运行时失败,因为任何东西都可以转换为对象(事实上,根本就没有转换;我只是为了对称而插入它)。

这并不是说在制作参数之前这样的调用代码不存在 Collection<?>:它当然可以,并且它在运行时已经失败了。但我要强调的一点是,使这个方法参数通用可能会给人一种错误的印象,即将现有的原始调用代码转换为使用泛型是安全的,这样做会导致它失败。

所以......除非你能保证在子类中没有这样的插入,或者你已经明确记录了不应该在第三种方法中修改集合,否则这种改变是不安全的。


(*)这是由于覆盖等价的定义而引起的 JLS Sec 8.4.2,明确考虑擦除。


9
2018-06-02 18:02



我似乎无法生成添加的代码 Integer.valueOf(1) 到了 Collection<?> 没有生产 编译时间 错误。这似乎对我有意义。你是否有一个代码片段的工作示例,这样做,编译,然后在运行时失败,因为我似乎无法产生这样的行为? - Silvio Mayolo
以供参考, 这里 是我尝试再现你描述的变化和行为。我刚刚链接的要点不能在我的机器上编译,我认为它不应该。 - Silvio Mayolo
@SilvioMayolo你不能。您可以将其添加到原始 Collection 但是,并将父方法的签名更改为 Collection<?> 不需要子类来添加 <?> 太。 - Andy Turner
啊,好吧。谢谢你的澄清!那是Java语言真正可怕的怪癖,但这至少是一种解释。 :( - Silvio Mayolo
@AndyTurner我有 向后兼容性 发行说明中的​​部分。这实际上是问题的原因。 - lexicore


您不会在运行时遇到任何问题,因为泛型类型会从二进制文件中删除 - 请参阅: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

你也不会在编译时遇到任何问题 Collection<?> 和 Collection 是等价的。 - 见: https://docs.oracle.com/javase/tutorial/extra/generics/legacy.html


5
2018-06-02 15:06





  1. Collection 是任何类型的集合(即它可以包含任何类型的元素:Integer,String,Object ...)
  2. Collection<?> 是一些特定类型的集合。

客户端代码不会出现任何编译错误或运行时错误。因为当你把一个集合传递给 Collection<?> 它被视为 Collection<Object>


1
2018-06-02 15:11