为什么菱形运算符在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 extends E>
Collection extends E>
。
这意味着addAll
接受任何包含任何未知类型的对象的集合,这些对象扩展了list
的类型。 这很好,因为这意味着您可以将List
到List
,但这会使我们的类型推断变得更加棘手。
实际上,它使类型推断不能在JLS当前规定的规则范围内工作。 在某些情况下,可以认为规则可以扩展到工作,但目前的规则意味着不这样做。
Type Inference文档中的解释似乎直接回答了这个问题(除非我遗漏了其他内容)。
Java SE 7及更高版本支持通用实例创建的有限类型推断; 如果构造函数的参数化类型在上下文中很明显,则只能使用类型推断。 例如,以下示例不编译:
List 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);
在编译方法调用时,javac首先需要知道参数的类型,然后才能确定哪个方法签名与它们匹配。 因此,在知道参数类型之前,不知道方法参数类型。
也许这可以改善; 截至今天,论证的类型与上下文无关。