Qual è il punto dell'operatore diamante (<>) in Java 7?


446

L'operatore Diamond in Java 7 consente un codice simile al seguente:

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

Tuttavia in Java 5/6, posso semplicemente scrivere:

List<String> list = new LinkedList();

La mia comprensione della cancellazione del tipo è che questi sono esattamente gli stessi. (Il generico viene comunque rimosso in fase di esecuzione).

Perché preoccuparsi del diamante? Quale nuova funzionalità / tipo di sicurezza consente? Se non offre alcuna nuova funzionalità, perché la menzionano come funzionalità? La mia comprensione di questo concetto è imperfetta?


4
Si noti che questo non è un problema se si utilizzano metodi factory statici, dal momento che Java digita inferenza sulle chiamate al metodo.
Brian Gordon,

quando disabiliti gli avvisi è in realtà inutile ... :) come per me
Renetik,

3
È stato risposto ma è anche nel tutorial Java (intorno alla metà della pagina): docs.oracle.com/javase/tutorial/java/generics/…
Andreas Tasoulas


2
Il mio punto di vista è che è zucchero sintattico per l'elenco <String> list = new LinkedList <Qualunque sia il tipo a sinistra> (); cioè mantenerlo generico
Viktor Mellgren il

Risposte:


497

Il problema con

List<String> list = new LinkedList();

è quello sul lato sinistro, stai usando il tipo genericoList<String> dove sul lato destro stai usando il tipo grezzoLinkedList . I tipi grezzi in Java esistono effettivamente solo per compatibilità con il codice pre-generico e non dovrebbero mai essere usati nel nuovo codice a meno che non sia assolutamente necessario.

Ora, se Java avesse generici dall'inizio e non avesse tipi, come quelli LinkedListche erano stati originariamente creati prima dei generici, probabilmente avrebbe potuto farlo in modo che il costruttore di un tipo generico infetti automaticamente i suoi parametri di tipo da sinistra lato dell'incarico, se possibile. Ma non è così, e deve trattare i tipi grezzi e quelli generici in modo diverso per la compatibilità con le versioni precedenti. Ciò lascia loro la necessità di creare un modo leggermente diverso , ma ugualmente conveniente, di dichiarare una nuova istanza di un oggetto generico senza dover ripetere i suoi parametri di tipo ... l'operatore diamante.

Per quanto riguarda l'esempio originale di List<String> list = new LinkedList(), il compilatore genera un avviso per quell'assegnazione perché deve. Considera questo:

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

I generici esistono per fornire protezione in fase di compilazione dal fare la cosa sbagliata. Nell'esempio sopra, l'utilizzo del tipo raw significa che non si ottiene questa protezione e verrà visualizzato un errore in fase di esecuzione. Questo è il motivo per cui non dovresti usare tipi grezzi.

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

L'operatore diamante, tuttavia, consente di definire il lato destro dell'assegnazione come una vera istanza generica con gli stessi parametri di tipo del lato sinistro ... senza dover digitare nuovamente tali parametri. Ti consente di mantenere la sicurezza dei generici con quasi lo stesso sforzo dell'utilizzo del tipo grezzo.

Penso che la cosa chiave da capire sia che i tipi grezzi (senza no <>) non possono essere trattati allo stesso modo dei tipi generici. Quando dichiari un tipo grezzo, non ottieni nessuno dei vantaggi e la verifica del tipo di generici. Devi anche tenere presente che i generici sono una parte generica del linguaggio Java ... non si applicano solo ai costruttori no-arg di Collections!


31
La compatibilità con le versioni precedenti è ottima, ma non a scapito della complessità, per favore. Perché Java 7 non può semplicemente introdurre lo -compatibilityswitch del compilatore mentre se assente javacproibirà tutti i tipi non elaborati e imporrà solo tipi strettamente generici? Ciò renderà i nostri codici meno dettagliati così com'è.
Rosdi Kasim,

3
@Rosdi: D'accordo, non è necessario per i tipi non elaborati nel nuovo codice. Tuttavia, preferirei fortemente includere il numero di versione Java nel file sorgente (anziché (mis) usando la riga di comando), vedi la mia risposta.
maaartinus,

37
Personalmente non mi piace l'uso dei diamanti A MENO CHE tu non stia definendo E creando un'istanza sulla stessa linea. List<String> strings = new List<>()va bene, ma se lo definisci private List<String> my list;, quindi a metà della pagina con cui crei un'istanza my_list = new List<>(), non è bello! Cosa contiene di nuovo la mia lista? Oh, lasciami cercare la definizione. All'improvviso, il vantaggio della scorciatoia di diamante ciao ciao.
rmirabelle,

11
@rmirabelle Come è molto diverso da: my_list = getTheList()? Esistono diversi modi migliori per affrontare questo tipo di problemi: 1. utilizzare un IDE che mostra i tipi di variabili al passaggio del mouse. 2. utilizzare nomi di variabili più significativi, come private List<String> strings3. non dividere la dichiarazione e l'inizializzazione delle variabili a meno che non sia necessario.
Natix,

1
@Morfidon: Sì, è ancora valido per Java 8. Sono abbastanza sicuro che dovresti ricevere avvisi.
ColinD

37

La tua comprensione è leggermente imperfetta. L'operatore del diamante è una bella caratteristica in quanto non è necessario ripeterti. Ha senso definire il tipo una volta quando lo dichiari, ma non ha senso definirlo di nuovo sul lato destro. Il principio DRY.

Ora per spiegare tutto il fuzz sulla definizione dei tipi. Hai ragione che il tipo viene rimosso in fase di runtime ma una volta che vuoi recuperare qualcosa da un elenco con la definizione del tipo lo ritorni come il tipo che hai definito quando dichiari l'elenco altrimenti perderebbe tutte le funzionalità specifiche e avrebbe solo il Funzionalità dell'oggetto tranne quando si esegue il cast dell'oggetto recuperato nel suo tipo originale, che a volte può essere molto complicato e provocare una ClassCastException.

Usando List<String> list = new LinkedList()otterrai avvisi rawtype.


8
List<String> list = new LinkedList()è il codice corretto. Lo sai e lo so anche io. E la domanda (per quanto ho capito) è: perché solo il compilatore Java non capisce che questo codice è abbastanza sicuro?
Romano,

22
@Roman: nonList<String> list = new LinkedList() è il codice corretto. Certo, sarebbe bello se fosse! E probabilmente avrebbe potuto essere se Java avesse generici sin dall'inizio e non avesse avuto a che fare con la retrocompatibilità di tipi generici che erano non generici, ma lo è.
ColinD,

6
@ColinD Java non ha davvero bisogno di gestire la retrocompatibilità in ogni singola riga . In qualsiasi file sorgente Java che utilizza generici, i vecchi tipi non generici dovrebbero essere vietati (è sempre possibile utilizzarli <?>se si interfaccia al codice legacy) e l'operatore inutile diamante non dovrebbe esistere.
maaartinus,

16

Questa riga provoca l'avviso [non selezionato]:

List<String> list = new LinkedList();

Quindi, la domanda si trasforma: perché l'avviso [non selezionato] non viene soppresso automaticamente solo per il caso in cui viene creata una nuova raccolta?

Penso che sarebbe un compito molto più difficile quindi aggiungere <>funzionalità.

UPD : Penso anche che ci sarebbe un disastro se fosse legalmente usare i tipi grezzi 'solo per poche cose'.


13

In teoria, l'operatore diamante consente di scrivere codice più compatto (e leggibile) salvando argomenti di tipo ripetuto. In pratica, sono solo due caratteri confusi in più che non ti danno nulla. Perché?

  1. Nessun programmatore sano utilizza tipi non elaborati nel nuovo codice. Quindi il compilatore potrebbe semplicemente supporre che non scrivendo argomenti di tipo si voglia inferirli.
  2. L'operatore Diamond non fornisce informazioni sul tipo, dice solo al compilatore "andrà bene". Quindi omettendolo non si può fare del male. In qualsiasi luogo in cui l'operatore del diamante è legale, potrebbe essere "dedotto" dal compilatore.

IMHO, avere un modo chiaro e semplice per contrassegnare una fonte come Java 7 sarebbe più utile che inventare cose così strane. Nel codice così marcato i tipi grezzi potrebbero essere vietati senza perdere nulla.

A proposito, non penso che dovrebbe essere fatto usando un interruttore di compilazione. La versione Java di un file di programma è un attributo del file, nessuna opzione. Usando qualcosa di banale come

package 7 com.example;

potrebbe chiarire (potresti preferire qualcosa di più sofisticato tra cui una o più parole chiave di fantasia). Consentirebbe anche di compilare insieme fonti scritte per diverse versioni di Java senza problemi. Consentirebbe l'introduzione di nuove parole chiave (ad es. "Modulo") o l'eliminazione di alcune funzionalità obsolete (più classi non nidificate non pubbliche in un singolo file o qualsiasi altra cosa) senza perdere alcuna compatibilità.


2
Hai considerato la differenza tra new ArrayList(anotherList)e new ArrayList<>(anotherList)(soprattutto se viene assegnato a List<String>ed anotherListè a List<Integer>)?
Paul Bellora,

@Paul Bellora: No. Sorprendentemente per me, compiliamo entrambi. Quello con i diamanti non ha nemmeno dato alcun avvertimento. Tuttavia, non riesco a vedere alcun senso in questo, puoi elaborare?
maaartinus,

Scusa, non ho spiegato molto bene. Vedi la differenza tra questi due esempi: ideone.com/uyHagh e ideone.com/ANkg3T Sto solo sottolineando che è importante utilizzare l'operatore diamante anziché un tipo non elaborato, almeno quando vengono passati argomenti con limiti generici in.
Paul Bellora

In realtà non mi ero preso il tempo di leggere la risposta di ColinD - cita praticamente la stessa cosa.
Paul Bellora,

2
Quindi, se stiamo per introdurre una nuova sintassi per i tipi grezzi, per i pochi posti in cui è realmente necessario, perché non usare qualcosa di simile new @RawType List(). Questa è già una sintassi Java 8 valida e le annotazioni dei tipi consentono di utilizzarlo in ogni luogo dove necessario, ad es @RawType List = (@RawType List) genericMethod();. Considerando che i tipi non elaborati attualmente creano un avviso del compilatore a meno che non @SuppressWarningssia stato inserito un appropriato , @RawTypesarebbe una sostituzione ragionevole e non è necessaria una sintassi più sottile.
Holger,

8

Durante la scrittura List<String> list = new LinkedList();, il compilatore produce un avviso "non selezionato". Potresti ignorarlo, ma se ignoravi questi avvisi potresti anche perdere un avviso che ti avvisa di un vero problema di sicurezza del tipo.

Quindi, è meglio scrivere un codice che non generi avvisi aggiuntivi e l'operatore Diamond ti consente di farlo in modo conveniente senza inutili ripetizioni.


4

Tutto quanto detto nelle altre risposte è valido ma i casi d'uso non sono completamente validi. Se uno controlla Guava e soprattutto le cose relative alle collezioni, lo stesso è stato fatto con metodi statici. Ad esempio Lists.newArrayList () che consente di scrivere

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

o con importazione statica

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

Guava ha altre funzionalità molto potenti come questa e in realtà non riesco a pensare a molti usi per <>.

Sarebbe stato più utile se si fosse scelto di rendere predefinito il comportamento dell'operatore diamante, ovvero il tipo è inferito dal lato sinistro dell'espressione o se il tipo del lato sinistro è stato inferito dal lato destro. Quest'ultimo è ciò che accade alla Scala.


3

Il punto per l'operatore Diamond è semplicemente ridurre la digitazione del codice quando si dichiarano tipi generici. Non ha alcun effetto sul runtime.

L'unica differenza se si specifica in Java 5 e 6,

List<String> list = new ArrayList();

è che si deve specificare @SuppressWarnings("unchecked")la list(altrimenti si otterrà un avvertimento incontrollato getto). La mia comprensione è che l'operatore del diamante sta cercando di semplificare lo sviluppo. Non ha assolutamente nulla a che fare con l'esecuzione runtime di generici.


non devi nemmeno usare quell'annotazione. Almeno in Eclipse, puoi semplicemente dire al compilatore di non avvisarti di questo e stai bene ...
Xerus

È meglio avere l'annotazione. Non tutti gli sviluppatori usano Eclipse qui.
Buhake Sindi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.