Jaki jest sens operatora diamentów (<>) w Javie 7?

445

Operator diamentów w java 7 zezwala na kod podobny do następującego:

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

Jednak w Javie 5/6 mogę po prostu napisać:

List<String> list = new LinkedList();

Rozumiem, że typ kasowania jest taki sam. (Generic i tak zostaje usunięty w czasie wykonywania).

Po co w ogóle zawracać sobie głowę diamentem? Jakie nowe funkcje / bezpieczeństwo typu pozwala? Jeśli nie daje żadnej nowej funkcjonalności, dlaczego wspominają o tym jako o funkcji? Czy moje rozumienie tej koncepcji jest wadliwe?

tofarr
źródło
4
Zauważ, że nie stanowi to problemu, jeśli używasz statycznych metod fabrycznych, ponieważ Java wywołuje wnioskowanie typu w wywołaniach metod.
Brian Gordon,
kiedy wyłączysz ostrzeżenie, jest to właściwie bezużyteczne ... :) jak dla mnie
Renetik
3
Odpowiedź została udzielona, ​​ale znajduje się również w samouczku Java (na środku strony): docs.oracle.com/javase/tutorial/java/generics/…
Andreas Tasoulas
Niezły artykuł na ten temat na dZone .
R. Oosterholt
2
Moim zdaniem jest to cukier składniowy dla List <String> list = new LinkedList <Bez względu na typ po lewej stronie> (); tzn. utrzymanie ogólnej wartości
Viktor Mellgren

Odpowiedzi:

496

Problem z

List<String> list = new LinkedList();

jest to, że po lewej stronie używasz typu ogólnego, aList<String> po prawej stronie używasz typu surowegoLinkedList . Surowe typy w Javie istnieją tylko dla kompatybilności z kodem pre-generics i nigdy nie powinny być używane w nowym kodzie, chyba że jest to absolutnie konieczne.

Teraz, jeśli Java miała generyczne od samego początku i nie miała typów, takich jak te LinkedList, które zostały pierwotnie utworzone, zanim miała generyczne, prawdopodobnie mogłaby to zrobić, aby konstruktor typu ogólnego automatycznie ustawiał parametry typu od lewej -ręczna strona zadania, jeśli to możliwe. Ale tak się nie stało i musi traktować typy surowe i typy ogólne inaczej dla kompatybilności wstecznej. To sprawia, że ​​muszą stworzyć nieco inny , ale równie wygodny sposób deklarowania nowej instancji obiektu ogólnego bez konieczności powtarzania parametrów typu ... operator diamentu.

Jeśli chodzi o oryginalny przykład List<String> list = new LinkedList(), kompilator generuje ostrzeżenie dla tego zadania, ponieważ musi. Rozważ to:

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

Istnieją środki generyczne, które zapewniają ochronę podczas kompilacji przed robieniem złych rzeczy. W powyższym przykładzie użycie typu raw oznacza, że ​​nie otrzymujesz tej ochrony i wystąpi błąd w czasie wykonywania. Dlatego nie powinieneś używać typów surowych.

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

Operator diamentów pozwala jednak zdefiniować prawą stronę przypisania jako prawdziwą ogólną instancję z tymi samymi parametrami typu co lewa strona ... bez konieczności ponownego wpisywania tych parametrów. Pozwala zachować bezpieczeństwo leków generycznych przy prawie takim samym wysiłku jak przy użyciu typu raw.

Myślę, że kluczową rzeczą do zrozumienia jest to, że typy surowe (bez <>) nie mogą być traktowane tak samo jak typy ogólne. Po zadeklarowaniu typu surowego nie uzyskuje się żadnych korzyści ani sprawdzania typu generyków. Trzeba też pamiętać, że generyczne są częścią języka Java ogólnego przeznaczenia ... nie dotyczą tylko konstruktorów bez argonu Collection!

ColinD
źródło
31
Kompatybilność wsteczna jest świetna, ale nie kosztem złożoności, proszę. Dlaczego Java 7 nie może po prostu wprowadzić -compatibilityprzełącznika kompilatora, a jeśli go nie javacma, zabroni wszystkich typów surowych i wymusi wyłącznie typy ściśle ogólne? To sprawi, że nasze kody będą mniej szczegółowe.
Rosdi Kasim
3
@Rosdi: Uzgodniony, nie ma potrzeby używania surowych typów w nowym kodzie. Jednak zdecydowanie wolałbym umieścić numer wersji Java w pliku źródłowym (zamiast (źle) używając wiersza poleceń), patrz moja odpowiedź.
maaartinus,
37
Osobiście nie podoba mi się użycie diamentów, chyba że definiujesz I tworzysz instancję na tej samej linii. List<String> strings = new List<>()jest OK, ale jeśli zdefiniujesz private List<String> my list;, a następnie w połowie strony, z której tworzona jest instancja my_list = new List<>(), nie będzie fajnie! Co znowu zawiera moja lista? Och, pozwól mi poszukać definicji. Nagle korzyść ze skrótu diamentowego zostaje do widzenia.
rmirabelle
11
@rmirabelle Jak to różni się od: my_list = getTheList()? Istnieje kilka lepszych sposobów radzenia sobie z tego rodzaju problemami: 1. użyj IDE, które pokazuje typy zmiennych po najechaniu myszą. 2. używaj bardziej znaczących nazw zmiennych, takich jak private List<String> strings3. nie dziel deklaracji i inicjalizacji zmiennych, chyba że naprawdę musisz.
Natix
1
@Morfidon: Tak, nadal działa w Javie 8. Jestem pewien, że powinieneś otrzymywać ostrzeżenia.
ColinD
36

Twoje zrozumienie jest nieco błędne. Operator diamentów jest fajną funkcją, ponieważ nie musisz się powtarzać. Wskazane jest zdefiniowanie typu raz, kiedy deklarujesz typ, ale po prostu nie ma sensu definiowanie go ponownie po prawej stronie. Zasada DRY.

Teraz wyjaśnię wszystkie problemy związane z definiowaniem typów. Masz rację, że typ jest usuwany w czasie wykonywania, ale gdy chcesz odzyskać coś z listy z definicją typu, odzyskujesz go jako typ zdefiniowany podczas deklarowania listy, w przeciwnym razie straciłby wszystkie określone funkcje i miałby tylko Funkcje obiektu, z wyjątkiem sytuacji, gdy odrzuciłeś pobrany obiekt do jego oryginalnego typu, co czasami może być bardzo trudne i skutkować wyjątkiem ClassCastException.

Użycie List<String> list = new LinkedList()spowoduje wyświetlenie ostrzeżeń typu raw.

Oktawian A. Damiean
źródło
8
List<String> list = new LinkedList()jest poprawnym kodem. Ty to wiesz i ja też to wiem. Pytanie (jak rozumiem) brzmi: dlaczego tylko kompilator Java nie rozumie, że ten kod jest dość bezpieczny?
Rzym.
22
@Roman: List<String> list = new LinkedList()jest nie prawidłowy kod. Jasne, byłoby miło, gdyby tak było! I prawdopodobnie mogłoby tak być, gdyby Java miała od początku generyczne i nie musiała zajmować się wsteczną kompatybilnością typów generycznych, które kiedyś nie były ogólne, ale tak jest.
ColinD
6
@ColinD Java naprawdę nie musi zajmować się kompatybilnością wsteczną w każdej linii . W jakimkolwiek pliku źródłowym Java używającym ogólnych, stare nieogólne typy powinny być zabronione (zawsze można użyć, <?>jeśli interfejs do starszego kodu), a bezużyteczny operator diamentów nie powinien istnieć.
maaartinus
16

Ta linia powoduje ostrzeżenie [niezaznaczone]:

List<String> list = new LinkedList();

Pytanie zmienia się: dlaczego [niezaznaczone] ostrzeżenie nie jest automatycznie tłumione tylko w przypadku, gdy tworzona jest nowa kolekcja?

Myślę, że byłoby to znacznie trudniejsze zadanie niż dodanie <>funkcji.

UPD : Myślę również, że byłoby bałagan, gdyby legalnie używać surowych typów „tylko dla kilku rzeczy”.

rzymski
źródło
13

Teoretycznie operator diamentów pozwala pisać bardziej zwarty (i czytelny) kod, zapisując argumenty typu powtarzanego. W praktyce to tylko dwa mylące znaki, które nie dają nic. Dlaczego?

  1. Żaden rozsądny programista nie używa surowych typów w nowym kodzie. Tak więc kompilator może po prostu założyć, że nie pisząc argumentów typu, chcesz je wywnioskować.
  2. Operator diamentów nie podaje żadnych informacji o typie, po prostu mówi kompilatorowi: „będzie dobrze”. Pomijając to, nie możesz wyrządzić szkody. W dowolnym miejscu, w którym operator diamentów jest legalny, kompilator może „wywnioskować”.

IMHO, mając jasny i prosty sposób na oznaczenie źródła jako Java 7, byłoby bardziej przydatne niż wymyślanie takich dziwnych rzeczy. W tak oznaczonym kodzie surowe typy mogą być zakazane bez utraty niczego.

Przy okazji, nie sądzę, że należy to zrobić za pomocą przełącznika kompilacji. Wersja pliku Java programu jest atrybutem pliku, bez żadnej opcji. Używanie czegoś tak trywialnego jak

package 7 com.example;

może to wyjaśnić (możesz preferować coś bardziej wyrafinowanego, w tym jedno lub więcej fantazyjnych słów kluczowych). Pozwoliłoby to nawet na kompilację źródeł napisanych dla różnych wersji Java bez żadnych problemów. Umożliwiłoby to wprowadzenie nowych słów kluczowych (np. „Moduł”) lub upuszczenie niektórych przestarzałych funkcji (wiele niepublicznych niepasowanych klas w jednym pliku lub w ogóle) bez utraty kompatybilności.

maaartinus
źródło
2
Czy zastanawiałeś się nad różnicą między ( new ArrayList(anotherList)a new ArrayList<>(anotherList)zwłaszcza jeśli jest on przypisany do List<String>i anotherListjest a List<Integer>)?
Paul Bellora
@ Paul Bellora: Nie. Zaskakujące dla mnie, oba kompilują. Ten z diamentami nawet nie otrzymał ostrzeżenia. Jednak nie widzę w tym żadnego sensu, możesz to rozwinąć?
maaartinus,
Przepraszam, nie wyjaśniłem zbyt dobrze. Zobacz różnicę między tymi dwoma przykładami: ideone.com/uyHagh i ideone.com/ANkg3T Po prostu zwracam uwagę, że użycie operatora diamentu zamiast surowego typu ma znaczenie, przynajmniej gdy przekazywane są argumenty z ogólnymi granicami w.
Paul Bellora
Właściwie nie poświęciłem czasu na przeczytanie odpowiedzi ColinD - cytuje on prawie to samo.
Paul Bellora,
2
Jeśli więc zamierzamy wprowadzić nową składnię dla typów surowych, dla kilku miejsc, w których jest to naprawdę potrzebne, dlaczego nie użyć czegoś takiego new @RawType List(). To już poprawna składnia Java 8 i adnotacje typu pozwalają na użycie jej w każdym miejscu, gdzie jest to potrzebne, np @RawType List = (@RawType List) genericMethod();. Biorąc pod uwagę, że typy surowe obecnie tworzą ostrzeżenie kompilatora, chyba że @SuppressWarningszostanie umieszczony odpowiedni, @RawTypebyłoby rozsądnym zamiennikiem i nie jest potrzebna bardziej subtelna składnia.
Holger
8

Podczas pisania List<String> list = new LinkedList();kompilator generuje ostrzeżenie „niezaznaczone”. Możesz to zignorować, ale jeśli zignorowałeś te ostrzeżenia, możesz również przeoczyć ostrzeżenie, które powiadamia o prawdziwym problemie z bezpieczeństwem.

Lepiej więc napisać kod, który nie generuje dodatkowych ostrzeżeń, a operator diamentów pozwala to zrobić w wygodny sposób bez zbędnego powtarzania.

axtavt
źródło
4

Wszystkie powiedziane w pozostałych odpowiedziach są prawidłowe, ale przypadki użycia nie są całkowicie poprawne IMHO. Jeśli ktoś sprawdzi Guawę, a zwłaszcza rzeczy związane ze zbiorami, to samo zrobiono metodami statycznymi. Np. Lists.newArrayList (), który pozwala pisać

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

lub ze statycznym importem

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

Guava ma inne bardzo potężne funkcje, takie jak ta, i właściwie nie mogę wymyślić wielu zastosowań dla <>.

Byłoby bardziej użyteczne, gdyby zdecydowali się na ustawienie domyślnego zachowania operatora diamentu, to znaczy, że typ jest wywnioskowany z lewej strony wyrażenia lub jeśli typ z lewej strony został wywnioskowany z prawej strony. To ostatnie dzieje się w Scali.

allprog
źródło
3

Celem operatora diamentowego jest po prostu ograniczenie pisania kodu podczas deklarowania typów ogólnych. Nie ma to żadnego wpływu na środowisko uruchomieniowe.

Jedyna różnica, jeśli podasz w Javie 5 i 6,

List<String> list = new ArrayList();

jest to, że trzeba podać @SuppressWarnings("unchecked")do list(inaczej dostaniesz niesprawdzony ostrzeżenie szarego). Rozumiem, że operator diamentów stara się ułatwić rozwój. Nie ma to w ogóle nic wspólnego z wykonywaniem generycznych programów uruchomieniowych.

Buhake Sindi
źródło
nie musisz nawet używać tej adnotacji. Przynajmniej w Eclipse możesz po prostu powiedzieć kompilatorowi, aby cię o tym nie ostrzegał i nic ci nie jest ...
Xerus
Lepiej mieć adnotację. Nie wszyscy programiści używają Eclipse tutaj.
Buhake Sindi