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


鉴于这个例子 泛型教程

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

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

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

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

请详细解释。


6830
2017-09-26 13:17


起源

“为什么以下不起作用?”有点弱。你到底想要什么?什么是错误信息?有许多原因“它不起作用”。您应该更准确地在问题中获得准确的答案。 - Arne Deutsch
download.oracle.com/javase/tutorial/java/generics/index.html 当你阅读并吸收它时回来。 - Matt Ball
实际上,从Java 7开始,这应该有效 - diamond operator,投票重新开放。 - Thomas
Pradeep,您使用的是Java 7吗?这是编译的先决条件。 - Thomas
@PradeepKumar:如果你是 不 那么使用Java 7 那 是您的代码无法编译的原因:本教程针对Java 7。 然而 您引用的特定代码甚至不能在Java 7上编译(出于其他原因)。 - Joachim Sauer


答案:


首先:除非您使用的是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。这很好,因为这意味着你可以 addAll 一个 List<Integer> 到了 List<Number>,但它使我们的类型推断更棘手。

实际上,它使类型推断不能在JLS当前规定的规则范围内工作。在 一些 情况可以说是规则 可以 延伸到工作,但目前的规则意味着不这样做。


14
2017-09-26 13:49



@ joachim.Thanks的答案 - Pradeep Kumar


来自的解释 类型推断 文档似乎直接回答了这个问题(除非我错过了其他的东西)。

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
2017-09-26 13:48



该教程并没有真正解释 为什么 钻石在这里不起作用。它只是手动,并说它“经常有效”。 - Joachim Sauer
@ Kal.Sorry惹恼了你。我明白了。你可以用简单的语言解释它。再一次 - Pradeep Kumar
同意。也许JLS说了些什么。 - Mister Smith
嗯..这对我来说很明显。它明确指出addAll期望Collection <?扩展E>你不能做,因为它可能是一个未知类型扩展列表的定义类型,因此推理将无法工作。 - Kal
@Kal:在这种情况下很容易被争论 可以 只是推断出类型 String 根据类型系统哪个是正确的,哪个会成功地允许通话。在我看来,这已经足够了。它似乎是一个类似旧的限制“为什么对象实例化不使用与方法调用相同的推理规则?”在Java 5和Java 6中(在Java 7中也是如此)。 - Joachim Sauer


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

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


0
2017-09-26 20:58