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

Pra*_*mar 11 java generics diamond-operator java-7

给出了泛型教程中的这个例子.

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

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

list.addAll(new ArrayList<>());
Run Code Online (Sandbox Code Playgroud)

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

请详细解释.

Joa*_*uer 14

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

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

当编译器可以自己找出类型时,菱形实际上是不必重复泛型类型信息的快捷方式.

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

List<String> list = new ArrayList<>(); // is a shortcut for
List<String> list = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

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

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

在这一行:

list.addAll(new ArrayList<>());
Run Code Online (Sandbox Code Playgroud)

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

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

这意味着addAll接受任何包含任何未知类型的对象的集合,这些对象扩展了我们的类型list.这很好,因为它意味着你可以addAlla List<Integer>到a List<Number>,但它使我们的类型推理更棘手.

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


Kal*_*Kal 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<>());
Run Code Online (Sandbox Code Playgroud)

请注意,菱形通常在方法调用中起作用;然而,为了更清楚起见,建议您主要使用菱形来初始化声明变量的地方

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

// The following statements compile:

List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);
Run Code Online (Sandbox Code Playgroud)

  • @Kal:很容易认为,在这种情况下,它*可以*简单地推断出“String”类型,根据类型系统,该类型是正确的,并且会成功允许调用。我认为这就足够了。这似乎是一个类似于旧的“为什么对象实例化不使用与方法调用相同的推理规则?”的限制。在 Java 5 和 Java 6 中也是如此(在 Java 7 中也是如此)。 (2认同)