Який сенс алмазного оператора (<>) у Java 7?


445

Діамантовий оператор java 7 дозволяє такий код:

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

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

List<String> list = new LinkedList();

Моє розуміння стирання типу - це те саме. (Узагальнений варіант все одно видаляється під час виконання).

Навіщо взагалі турбуватися з діамантом? Які нові функціональні можливості / типи безпеки це дозволяє? Якщо він не дає ніяких нових функціональних можливостей, чому вони згадують його як особливість? Чи моє розуміння цього поняття хибно?


4
Зауважте, що це не проблема, якщо ви використовуєте статичні заводські методи, оскільки Java вводить умовивід при викликах методів.
Брайан Гордон

коли ви відключите попередження, це насправді марно ... :) як для мене
Renetik

3
На нього відповіли, але він є також у навчальному посібнику Java (приблизно в середині сторінки): docs.oracle.com/javase/tutorial/java/generics/…
Андреас Тасулас,

Приємна стаття про це на dZone .
Р. Остерхольт

2
Я вважаю, що це синтаксичний цукор для списку <String> list = новий LinkedList <Незалежно від типу зліва> (); тобто зберегти це загальне
Віктор Мелгрен

Відповіді:


496

Випуск с

List<String> list = new LinkedList();

це те, що в лівій частині ви використовуєте загальний тип, List<String>де в правій частині ви використовуєте необроблений тип LinkedList. Сировинні типи в Java фактично існують лише для сумісності з кодом попереднього генерику і ніколи не повинні використовуватися в новому коді, якщо ви абсолютно не повинні.

Тепер, якщо у Java були дженерики з самого початку і не було таких типів, як, наприклад LinkedList, спочатку створені до того, як у них були дженерики, можливо, це могло б зробити так, що конструктор для загального типу автоматично виводить параметри свого типу зліва -ручна сторона завдання, якщо це можливо. Але це не відбулося, і він повинен по-різному ставитися до вихідних і загальних типів для зворотної сумісності. Це залишає для них необхідність зробити дещо інший , але не менш зручний спосіб декларування нового екземпляра родового об'єкта без необхідності повторювати його параметри типу ... оператор алмазу.

Що стосується вашого оригінального прикладу 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);

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

Я думаю, що головне, що потрібно зрозуміти, - це те, що сировинні типи (без <>) не можна трактувати так само, як загальні типи. Коли ви декларуєте вихідний тип, ви не отримуєте жодної переваги та перевірки типу генеричних даних. Ви також повинні мати на увазі, що дженерики є частиною загальної мети мови Java ... вони не стосуються лише no-arg конструкторів Collections!


31
Зворотна сумісність чудова, але не за рахунок складності, будь ласка. Чому Java 7 не могла просто запровадити -compatibilityкомпілятор компілятора, тоді як, якщо він відсутній javac, заборонятимуть усі необрабовані типи та застосовуватимуть лише строго загальні типи? Це зробить наші коди менш багатослівними, як це є.
Росді Касім

3
@Rosdi: Погоджено, в новому коді немає необхідності в сирових типах. Однак я б наголосив включити номер версії Java у вихідний файл (замість (mis) за допомогою командного рядка), дивіться мою відповідь.
maaartinus

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. не розбивайте декларацію та ініціалізацію змінних, якщо вам це не потрібно.
Натікс

1
@Morfidon: Так, він все ще діє для Java 8. Я впевнений, що вам слід отримувати попередження.
ColinD

36

Ваше розуміння є дефектом. Діамантовий оператор - це приємна особливість, оскільки не потрібно повторювати себе. Є сенс визначити тип один раз, коли ви декларуєте тип, але просто не має сенсу визначати його знову з правого боку. Принцип DRY

Тепер пояснимо всі нечітки щодо визначення типів. Ви маєте рацію, що тип видалено під час виконання, але як тільки ви захочете отримати щось із списку з визначенням типу, ви отримаєте його назад як тип, який ви визначили при оголошенні списку, інакше він втратить усі конкретні функції та матиме лише Особливості об'єкта, за винятком випадків, коли ви передали отриманий об'єкт оригінальному типу, який іноді може бути дуже складним і призводити до класу ClassCastException.

Використовуючи List<String> list = new LinkedList(), ви отримаєте попередження про вихідний тип.


8
List<String> list = new LinkedList()- правильний код. Ви це знаєте, і я це теж знаю. І питання (наскільки я це розумію) таке: чому лише компілятор Java не розуміє, що цей код досить безпечний?
Роман

22
@Roman: List<String> list = new LinkedList()це НЕ правильний код. Звичайно, було б добре, якби було! І це, мабуть, могло б бути, якби у Java були дженерики з самого початку і не довелося мати справу з зворотною сумісністю загальних типів, які раніше були неродовими, але це так.
ColinD

6
@ColinD Java справді не має потреби мати зворотну сумісність у кожному окремому рядку . У будь-якому вихідному файлі Java, що використовує дженерики, старі негенеричні типи повинні бути заборонені (ви завжди можете користуватися, <?>якщо взаємодіяти зі старим кодом), а непридатний оператор алмазу не повинен існувати.
maaartinus

16

Цей рядок викликає попередження [невірно]:

List<String> list = new LinkedList();

Отже, питання трансформується: чому попередження попередженого режиму не придушується автоматично лише у випадку створення нової колекції?

Я думаю, було б набагато складніше завдання, ніж додавання <> функції.

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


13

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

  1. Жоден здоровий програміст не використовує вихідні типи в новому коді. Таким чином, компілятор може просто припустити, що, не записуючи аргументів типу, ви хочете, щоб вони їх зробили.
  2. Діамантовий оператор не надає інформації про тип, він просто каже компілятору, "це буде добре". Тож, опустивши його, ви можете не нашкодити. У будь-якому місці, де алмазний оператор легальний, компілятор може "зробити висновок".

IMHO, мати чіткий і простий спосіб позначити джерело як Java 7 було б корисніше, ніж вигадувати такі дивні речі. У такому маркованому коді необроблені типи можуть бути заборонені, нічого не втрачаючи.

До речі, я не думаю, що це потрібно робити за допомогою перемикача компіляції. Версія Java для програмного файлу є атрибутом файлу, взагалі немає опції. Використовуючи щось настільки тривіальне, як

package 7 com.example;

це може зрозуміти (ви можете віддати перевагу щось більш складне, включаючи одне або декілька вигадливих ключових слів). Це навіть дозволило б без проблем збирати джерела, написані для різних версій Java. Це дозволить ввести нові ключові слова (наприклад, "модуль") або видалити деякі застарілі функції (кілька непублічних невкладених класів в одному файлі чи будь-якому іншому), не втрачаючи сумісності.


2
Чи розглядали ви різницю між new ArrayList(anotherList)і new ArrayList<>(anotherList)(особливо, якщо він призначений List<String>і anotherListє a List<Integer>)?
Пол Беллора

@Paul Bellora: Ні. Дивно для мене, обидва складають. Той, з діамантами, навіть не давав попередження. Однак я не бачу сенсу в цьому, можете ви докладно?
maaartinus

Вибачте, я не дуже добре пояснив. Дивіться різницю між цими двома прикладами: ideone.com/uyHagh та ideone.com/ANkg3T Я просто вказую , що має значення використовувати оператор алмазів замість необробленого типу, принаймні, коли аргументи із загальними межами передаються в.
Пол Беллора

Насправді я не встиг прочитати відповідь ColinD - він цитує майже те саме.
Пол Беллора

2
Отже, якщо ми збираємося ввести новий синтаксис для сировинних типів, то для кількох місць, де це дійсно потрібно, чому б не використати щось подібне new @RawType List(). Це вже дійсний синтаксис Java 8 і анотації типу дозволяють використовувати його в будь-якому місці, де потрібно, наприклад @RawType List = (@RawType List) genericMethod();. Зважаючи на те, що в даний час необроблені типи створюють попередження компілятора, якщо @SuppressWarningsне було розміщено відповідне, @RawTypeбуло б розумною заміною і більш тонкий синтаксис не потрібен.
Холгер

8

Коли ви пишете List<String> list = new LinkedList();, компілятор видає попередження "без перевірки". Ви можете ігнорувати це, але якщо ви ігнорували ці попередження, ви також можете пропустити попередження, яке повідомляє вас про реальну проблему безпеки.

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


4

Усі сказані в інших відповідях є дійсними, але випадки використання не є повністю дійсними IMHO. Якщо хтось перевіряє 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");

У Guava є такі дуже потужні функції, як ця, і я насправді не можу придумати багато можливостей для <>.

Було б корисніше, якби вони зробили поведінку оператора алмазу за замовчуванням, тобто тип виводили з лівого боку виразу або якщо тип лівої сторони було виведено з правого боку. Останнє - це те, що відбувається у Scala.


3

Сенс для оператора алмазів - просто зменшити введення коду при оголошенні загальних типів. Це ніяк не впливає на час виконання.

Єдина відмінність, якщо ви вкажете в Java 5 і 6,

List<String> list = new ArrayList();

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


вам навіть не потрібно використовувати цю анотацію. Принаймні, у Eclipse, ви можете просто сказати компілятору не попереджати про це, і ви все добре ...
Xerus

Краще мати анотацію. Тут не всі розробники використовують Eclipse.
Buhake Sindi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.