Почему оператор алмаза не работает в вызове addAll() в 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<>());

Почему последняя строка не компилируется, когда кажется, что она должна компилироваться. Первая строка использует очень похожую конструкцию и компилируется без проблем.

Пожалуйста, объясните подробно.

Ответ 1

Прежде всего: если вы не используете 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 a List<Integer> на List<Number>, но это делает наш вывод типа более сложным.

Фактически это делает вывод типа не работать в рамках правил, установленных в настоящее время JLS. В некоторых ситуациях можно утверждать, что правила могут быть расширены для работы, но текущие правила подразумевают, что это не делается.

Ответ 2

Объяснение из документации Type Inference похоже на ответ на этот вопрос напрямую (если только я не пропустил что-то еще).

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);

Ответ 3

При компиляции вызова метода javac сначала должен знать тип аргументов, прежде чем определять, какая подпись метода соответствует им. Таким образом, тип параметра метода неизвестен до того, как известен тип аргумента.

Возможно, это можно улучшить; на сегодняшний день тип аргумента не зависит от контекста.