为什么Java 7中的addAll()方法中不能使用钻石操作符?

11

以下是来自泛型教程的示例。

List<String> list = new ArrayList<>();
list.add("A");

// The following statement should fail since addAll expects
// Collection<? extends String>

list.addAll(new ArrayList<>());
为什么最后一行代码看起来应该能编译,但实际上无法通过编译。而第一行使用了非常类似的结构却没有问题。请详细解释。

1
“为什么以下代码不起作用?”这个问题有点含糊。你具体期望什么?还有,错误信息是什么?“不起作用”有很多原因。你应该更精确地提出问题,以获得精确的答案。 - Arne Deutsch
1
当你阅读并吸收了http://download.oracle.com/javase/tutorial/java/generics/index.html,请回来。 - Matt Ball
1
@Thomas:不,它不应该。钻石操作符在这种情况下不起作用。而且为什么它不起作用实际上是非常复杂的。这就是为什么我认为这个问题应该重新开放的原因。 - Joachim Sauer
2
Pradeep,你在使用Java 7吗?那是编译的先决条件。 - Thomas
3
如果你没有使用Java 7,那么这就是你的代码无法编译的原因:该教程针对Java 7。然而,你引用的特定代码即使在Java 7上也无法编译(由于其他原因)。 - Joachim Sauer
显示剩余12条评论
3个回答

14
首先:除非您使用Java 7,否则所有这些都不起作用,因为菱形<>仅在该Java版本中引入。
此外,本答案假定读者了解泛型的基础知识。如果您不了解,请阅读教程的其他部分,并在理解后返回。
实际上,菱形是一种快捷方式,可以在编译器可以自行找出类型时,无需重复通用类型信息。
最常见的用例是在同一行中定义变量并初始化变量时。
List<String> list = new ArrayList<>(); // is a shortcut for
List<String> list = new ArrayList<String>();

在这个例子中,差别并不大,但是一旦你涉及到Map<String, ThreadLocal<Collection<Map<String,String>>>>,它将成为一个重大的改进(注意:我不建议实际使用这样的结构!)。
问题在于规则只能做到这一步。在上面的例子中,很明显应该使用什么类型,编译器和开发人员都同意。
在这一行:
list.addAll(new ArrayList<>());

看起来很明显,至少开发人员知道类型应该是String

然而,查看Collection.addAll()的定义,我们可以看到参数类型为Collection<? extends E>

这意味着addAll接受包含任何未知类型对象的任何集合,这些对象扩展了我们list的类型。这很好,因为这意味着您可以将List<Integer>添加到List<Number>中,但它使我们的类型推断变得更加棘手。

实际上,它使得目前由JLS制定的规则内的类型推断无法工作。在某些情况下,可以认为规则可以被扩展以解决此问题,但当前的规则暗示不要这样做。


@joachim。感谢您的答案。 - Pradeep Kumar

1

类型推断文档中的解释似乎直接回答了这个问题(除非我漏掉了其他内容)。

Java SE 7及更高版本支持有限的泛型实例创建类型推断;只有在构造函数的参数化类型在上下文中明显时才能使用类型推断。例如,以下示例无法编译:

List<String> list = new ArrayList<>();
list.add("A");

  // The following statement should fail since addAll expects
  // Collection<? extends String>

list.addAll(new ArrayList<>());

请注意,钻石语法经常用于方法调用;然而,为了更清晰明了,建议您主要在声明变量时使用钻石语法进行初始化。
相比之下,以下示例可以编译:
// The following statements compile:

List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);

1
教程并没有真正解释为什么这里不能使用菱形语法。 它只是含糊其辞地说“经常有效”。 - Joachim Sauer
嗯... 对我来说似乎很明显。它清楚地说明了addAll期望Collection<? extends E>,但你不能这样做,因为它可能是一个未知类型扩展列表的定义类型,因此推断无法工作。 - Kal
2
@Kal:可以很容易地争论,在这种情况下它可能只是推断出类型String,这将根据类型系统是正确的,并且将成功允许调用。在我看来,这就足够了。这似乎是类似于旧版Java 5和Java 6中的“为什么对象实例化不使用在方法调用中使用的推断规则?”的限制(在Java 7中仍然成立)。 - Joachim Sauer
@Kal. 这里 list.addAll(list2) 是如何工作的?这里我们也没有指定 ArrayList 的类型。 - Pradeep Kumar
@PRadeep--这句话回应了Joachim之前说的。钻石操作符能够从构造函数定义中推断类型。 - Kal
显示剩余5条评论

0

在编译方法调用时,javac 需要先知道参数的类型,然后才能确定哪个方法签名与之匹配。因此,在了解参数类型之前,方法参数类型是未知的。

也许这可以得到改进;截至今天,参数的类型与上下文无关。


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