В чем смысл бриллиантового оператора (<>) в Java 7?

446

Алмазный оператор в Java 7 позволяет код, подобный следующему:

List<String> list = new LinkedList<>();

Однако в Java 5/6 я могу просто написать:

List<String> list = new LinkedList();

Я понимаю, что стирание типа это то же самое. (Универсальный в любом случае удаляется во время выполнения).

Зачем вообще беспокоиться о бриллианте? Какие новые функциональные возможности / безопасность типов это позволяет? Если это не дает никакой новой функциональности, почему они упоминают это как функцию? Мое понимание этой концепции неверно?

tofarr
источник
4
Обратите внимание, что это не проблема, если вы используете статические фабричные методы, так как Java делает вывод типов при вызовах методов.
Брайан Гордон
когда вы отключаете предупреждение, это на самом деле бесполезно ... :) как для меня
Renetik
3
На него дан ответ, но он также есть в руководстве по Java (примерно в середине страницы): docs.oracle.com/javase/tutorial/java/generics/…
Андреас Тасулас
Хорошая статья об этом на dZone .
Р. Остерхолт
2
На мой взгляд, это синтаксический сахар для List <String> list = new LinkedList <Независимо от того, какой тип слева является> (); то есть держать его родовым
Виктор Меллгрен

Ответы:

497

Вопрос с

List<String> list = new LinkedList();

в том, что с левой стороны вы используете универсальный тип, а List<String>с правой стороны вы используете необработанный тип LinkedList. Необработанные типы в Java эффективно существуют только для совместимости с предварительным универсальным кодом и никогда не должны использоваться в новом коде, если только вам это не нужно.

Теперь, если у Java были обобщения с самого начала и не было типов, таких как LinkedList, которые были изначально созданы до того, как у него были обобщения, это, вероятно, могло бы сделать это так, чтобы конструктор для обобщенного типа автоматически определял параметры своего типа слева - стороны задания, если это возможно. Но это не так, и он должен обрабатывать необработанные типы и универсальные типы по-разному для обратной совместимости. Это оставляет им необходимость сделать немного другой , но одинаково удобный способ объявления нового экземпляра универсального объекта без необходимости повторять его параметры типа ... оператор diamond.

Насколько ваш оригинальный пример List<String> list = new LinkedList(), компилятор генерирует предупреждение для этого назначения, потому что он должен. Учти это:

List<String> strings = ... // some list that contains some strings

// Totally legal since you used the raw type and lost all type checking!
List<Integer> integers = new LinkedList(strings);

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

// Not legal since the right side is actually generic!
List<Integer> integers = new LinkedList<>(strings);

Оператор diamond, однако, позволяет определять правую часть назначения как истинный универсальный экземпляр с такими же параметрами типа, как у левой стороны ... без необходимости повторного ввода этих параметров. Это позволяет сохранить безопасность дженериков практически с теми же усилиями, что и при использовании необработанного типа.

Я думаю, что ключевым моментом для понимания является то, что необработанные типы (без <>) нельзя рассматривать так же, как универсальные типы. Когда вы объявляете необработанный тип, вы не получаете ни одного из преимуществ и проверки типа обобщений. Вы также должны помнить, что дженерики являются частью общего назначения языка Java ... они не просто применяются к конструкторам без аргументов Collections!

ColinD
источник
31
Обратная совместимость отличная, но не за счет сложности, пожалуйста. Почему Java 7 не может просто ввести -compatibilityпереключение компилятора, тогда как если оно отсутствует, то javacзапретит все необработанные типы и будет применять только строго общие типы? Это сделает наши коды менее многословными.
Росди Касим
3
@Rosdi: Согласен, в новом коде нет необходимости в необработанных типах. Тем не менее, я бы предпочел включить номер версии Java в исходный файл (вместо (неправильно) с помощью командной строки), смотрите мой ответ.
Маартин
37
Мне лично не нравится использование бриллиантов, если вы не определяете и не создаете экземпляры на одной линии. List<String> strings = new List<>()Это нормально, но если вы определите private List<String> my list;, а затем на полпути вниз по странице, с которой вы создаете экземпляр my_list = new List<>(), это не круто! Что мой список содержит снова? О, позвольте мне поискать определение. Внезапно, преимущество алмазного ярлыка прощается.
rmirabelle
11
@rmirabelle Как это отличается от: my_list = getTheList()? Есть несколько лучших способов решения таких проблем: 1. Используйте IDE, которая показывает типы переменных при наведении курсора мыши. 2. используйте более значимые имена переменных, такие как private List<String> strings3. не разделяйте объявление и инициализацию переменных, если только вам это не нужно.
Natix
1
@Morfidon: Да, это все еще действует для Java 8. Я почти уверен, что вы должны получать предупреждения.
ColinD
37

Ваше понимание слегка ошибочно. Оператор с бриллиантами - хорошая функция, так как вам не нужно повторяться. Имеет смысл определять тип один раз, когда вы объявляете тип, но просто не имеет смысла определять его снова с правой стороны. СУХОЙ принцип.

Теперь объясним весь паз об определении типов. Вы правы в том, что тип удаляется во время выполнения, но как только вы захотите извлечь что-то из списка с определением типа, вы получите его обратно как тип, который вы определили при объявлении списка, иначе он потеряет все специфические функции и будет иметь только Возможности объекта, за исключением случаев, когда вы приводите извлеченный объект к его исходному типу, который иногда может быть очень сложным и привести к исключению ClassCastException.

Используя List<String> list = new LinkedList()вы получите необработанные предупреждения.

Октавиан А. Дамиан
источник
8
List<String> list = new LinkedList()правильный код Вы знаете это, и я знаю это тоже. И вопрос (насколько я понимаю) таков: почему только Java-компилятор не понимает, что этот код достаточно безопасен?
Роман
22
@Роман: List<String> list = new LinkedList() это не правильный код. Конечно, было бы хорошо, если бы это было! И это, вероятно, могло бы произойти, если бы в Java изначально были дженерики, и им не приходилось иметь дело с обратной совместимостью дженериков, которые раньше были неуниверсальными, но это так.
ColinD
6
@ColinD Java действительно не нужно иметь дело с обратной совместимостью в каждой отдельной строке . В любом исходном файле Java, использующем дженерики, старые неуниверсальные типы должны быть запрещены (вы всегда можете использовать их, <?>если связаны с унаследованным кодом), а бесполезный оператор diamond не должен существовать.
Maaartinus
16

Эта строка вызывает предупреждение [unchecked]:

List<String> list = new LinkedList();

Итак, вопрос трансформируется: почему предупреждение [unchecked] не подавляется автоматически только для случая, когда создается новая коллекция?

Я думаю, это было бы гораздо более сложной задачей, чем добавление <>функции.

UPD : Я также думаю, что был бы беспорядок, если бы юридически использовались необработанные типы «только для нескольких вещей».

Роман
источник
13

Теоретически, оператор diamond позволяет писать более компактный (и читаемый) код, сохраняя аргументы повторного типа. На практике это только два запутанных символа, которые больше ничего не дают. Почему?

  1. Ни один здравомыслящий программист не использует необработанные типы в новом коде. Таким образом, компилятор может просто предположить, что, не записывая аргументы типа, вы хотите, чтобы он выводил их.
  2. Оператор diamond не предоставляет никакой информации о типе, он просто говорит компилятору: «Все будет хорошо». Таким образом, опуская это вы не можете причинить вреда В любом месте, где бриллиантовый оператор допустим, он может быть «выведен» компилятором.

ИМХО, наличие ясного и простого способа пометить источник как Java 7 было бы более полезным, чем придумывать такие странные вещи. В таком маркированном коде необработанные типы могут быть запрещены, не теряя ничего.

Кстати, я не думаю, что это должно быть сделано с помощью переключателя компиляции. Java-версия программного файла является атрибутом файла, никакой опции вообще нет. Используя что-то тривиальное, как

package 7 com.example;

может прояснить это (вы можете предпочесть что-то более сложное, включая одно или несколько необычных ключевых слов). Это даже позволило бы без проблем скомпилировать исходники, написанные для разных версий Java. Это позволило бы вводить новые ключевые слова (например, «модуль») или отбрасывать некоторые устаревшие функции (несколько непубличных, не вложенных классов в одном файле или вообще) без потери какой-либо совместимости.

maaartinus
источник
2
Рассматривали ли вы разницу между new ArrayList(anotherList)и new ArrayList<>(anotherList)(особенно, если она назначается List<String>и anotherListявляется List<Integer>)?
Пол Беллора
@Paul Bellora: Нет. Удивительно для меня, оба компиляции. Тот, с бриллиантами, даже не дал предупреждения. Тем не менее, я не вижу никакого смысла в этом, вы можете уточнить?
Maaartinus
Извините, я не очень хорошо объяснил. Посмотрите на разницу между этими двумя примерами: ideone.com/uyHagh и ideone.com/ANkg3T. Я просто отмечаю , что имеет значение использовать оператор diamond вместо необработанного типа, по крайней мере, когда передаются аргументы с общими границами. в.
Пол Беллора
На самом деле я не нашел времени, чтобы прочитать ответ Колинда - он цитирует почти то же самое.
Пол Беллора
2
Итак, если мы собираемся ввести новый синтаксис для необработанных типов, для тех немногих мест, где это действительно необходимо, почему бы не использовать что-то подобное new @RawType List(). Это уже допустимый синтаксис Java 8 и аннотации типов позволяют использовать его в любом месте, где это необходимо, например @RawType List = (@RawType List) genericMethod();. Учитывая, что необработанные типы в настоящее время создают предупреждение компилятора, если @SuppressWarningsне было помещено соответствующее, @RawTypeбыло бы разумной заменой, и более тонкий синтаксис не требуется.
Хольгер
8

Когда вы пишете List<String> list = new LinkedList();, компилятор выдает «непроверенное» предупреждение. Вы можете игнорировать это, но если вы раньше игнорировали эти предупреждения, вы также можете пропустить предупреждение, уведомляющее вас о реальной проблеме безопасности типа.

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

axtavt
источник
4

Все сказанное в других ответах является действительным, но варианты использования не полностью действительны ИМХО. Если кто-то проверяет Guava и особенно вещи, связанные с коллекциями, то же самое было сделано со статическими методами. Например, Lists.newArrayList (), который позволяет писать

List<String> names = Lists.newArrayList();

или со статическим импортом

import static com.google.common.collect.Lists.*;
...
List<String> names = newArrayList();
List<String> names = newArrayList("one", "two", "three");

У Гуавы есть и другие очень мощные функции, подобные этой, и я на самом деле не могу придумать много вариантов использования <>.

Было бы более полезно, если бы они использовали поведение по умолчанию для оператора diamond, то есть тип определялся с левой стороны выражения или если тип левой стороны был выведен с правой стороны. Последнее то, что происходит в Scala.

allprog
источник
3

Смысл для оператора diamond - просто уменьшить число типов кода при объявлении универсальных типов. Это никак не влияет на время выполнения.

Единственное отличие, если вы укажете в Java 5 и 6,

List<String> list = new ArrayList();

является то, что вы должны указать @SuppressWarnings("unchecked")для list(в противном случае вы получите непроверенное предупреждение о приведении). Насколько я понимаю, оператор бриллиантов пытается облегчить разработку. Он не имеет ничего общего с исполнением обобщений во время выполнения.

Бухаке синди
источник
вам даже не нужно использовать эту аннотацию. По крайней мере, в Eclipse вы можете просто сказать компилятору не предупреждать вас об этом, и у вас все хорошо ...
Xerus
Лучше иметь аннотацию. Не каждый разработчик использует Eclipse здесь.
Бухаке Синди