为什么菱形运算符在Java 7中的addAll()调用中不起作用?

给出了generics教程中的这个例子。

List list = new ArrayList(); list.add("A"); // The following statement should fail since addAll expects // Collection list.addAll(new ArrayList()); 

为什么最后一行不能编译,它似乎应该编译。 第一行使用非常相似的构造并且编译没有问题。

请详细解释。

首先:除非您使用Java 7,否则所有这些都不起作用,因为仅在该Java版本中引入了菱形<>

此外,这个答案假定读者理解generics的基础知识 。 如果不这样做,请阅读教程的其他部分 ,并在理解这些部分后再回来。

钻石实际上是一种快捷方式,当编译器可以自己找出类型时,不必重复generics类型信息。

最常见的用例是当变量在其初始化的同一行中定义时:

 List list = new ArrayList<>(); // is a shortcut for List list = new ArrayList(); 

在这个例子中差异不大,但是一旦你得到Map>>>它将是一个重要的增强(注意:我鼓励实际使用这样的结构体!)。

问题是规则只走了那么远。 在上面的例子中,很明显应该使用什么类型,编译器和开发人员都同意。

在这一行:

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

似乎是显而易见的。 至少开发人员知道类型应该是String

但是,看一下Collection.addAll()的定义,我们看到参数类型是Collection Collection

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

实际上,它使类型推断不能在JLS当前规定的规则范围内工作。 在某些情况下,可以认为规则可以扩展到工作,但目前的规则意味着不这样做。

Type Inference文档中的解释似乎直接回答了这个问题(除非我遗漏了其他内容)。

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

 List list = new ArrayList<>(); list.add("A"); // The following statement should fail since addAll expects // Collection list.addAll(new ArrayList<>()); 

请注意,钻石通常用于方法调用; 但是,为了更加清晰,建议您主要使用钻石来初始化声明它的变量

相比之下,以下示例编译:

 // The following statements compile: List list2 = new ArrayList<>(); list.addAll(list2); 

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

也许这可以改善; 截至今天,论证的类型与上下文无关。