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

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

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

3 ответа

Прежде всего: если вы не используете 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. В некоторых ситуациях можно утверждать, что правила могут быть расширены для работы, но текущие правила подразумевают, что не делают этого.

Похоже, что объяснение из документации " Вывод типа" дает прямой ответ на этот вопрос (если я не пропускаю что-то еще)

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

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

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

Другие вопросы по тегам