Was ist der Sinn des Diamantoperators (<>) in Java 7?

445

Der Diamantoperator in Java 7 erlaubt Code wie den folgenden:

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

In Java 5/6 kann ich jedoch einfach schreiben:

List<String> list = new LinkedList();

Mein Verständnis von Typlöschung ist, dass diese genau gleich sind. (Das Generikum wird sowieso zur Laufzeit entfernt).

Warum sich überhaupt mit dem Diamanten beschäftigen? Welche neue Funktionalität / Typensicherheit erlaubt es? Wenn es keine neuen Funktionen bietet, warum wird es dann als Feature erwähnt? Ist mein Verständnis dieses Konzepts fehlerhaft?

tofarr
quelle
4
Beachten Sie, dass dies kein Problem darstellt, wenn Sie statische Factory-Methoden verwenden, da Java bei Methodenaufrufen Inferenzen eingibt.
Brian Gordon
Wenn Sie die Warnung deaktivieren, ist es tatsächlich nutzlos ... :) wie für mich
Renetik
3
Es wurde beantwortet, aber es ist auch im Java-Tutorial (ungefähr in der Mitte der Seite): docs.oracle.com/javase/tutorial/java/generics/…
Andreas Tasoulas
Schöner Artikel dazu auf dZone .
R. Oosterholt
2
Meiner Ansicht nach handelt es sich um syntaktischen Zucker für List <String> list = new LinkedList <Was auch immer der Typ links ist> (); dh es generisch zu halten
Viktor Mellgren

Antworten:

496

Das Problem mit

List<String> list = new LinkedList();

ist, dass Sie auf der linken Seite den generischen Typ verwenden, List<String>während Sie auf der rechten Seite den Rohtyp verwendenLinkedList . Raw-Typen in Java existieren effektiv nur aus Gründen der Kompatibilität mit vorgenerischem Code und sollten niemals in neuem Code verwendet werden, es sei denn, Sie müssen dies unbedingt tun.

Wenn Java von Anfang an Generika hatte und keine Typen hatte, wie sie LinkedListursprünglich erstellt wurden, bevor es Generika hatte, hätte es wahrscheinlich so gemacht werden können, dass der Konstruktor für einen generischen Typ seine Typparameter automatisch von links ableitet -hand Seite der Aufgabe, wenn möglich. Dies ist jedoch nicht der Fall, und aus Gründen der Abwärtskompatibilität müssen Rohtypen und generische Typen unterschiedlich behandelt werden. Das lässt sie etwas anders machen müssen , aber ebenso bequeme Methode zum Deklarieren einer neuen Instanz eines generischen Objekts festlegen, ohne die Typparameter wiederholen zu müssen ... den Diamantoperator.

In Ihrem ursprünglichen Beispiel List<String> list = new LinkedList()generiert der Compiler eine Warnung für diese Zuweisung, da dies erforderlich ist. Bedenken Sie:

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

Es gibt Generika, die während der Kompilierung Schutz vor Falschem bieten. Im obigen Beispiel bedeutet die Verwendung des Rohtyps, dass Sie diesen Schutz nicht erhalten und zur Laufzeit einen Fehler erhalten. Aus diesem Grund sollten Sie keine Rohtypen verwenden.

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

Der Diamantoperator ermöglicht jedoch, dass die rechte Seite der Zuweisung als echte generische Instanz mit denselben Typparametern wie die linke Seite definiert wird ... ohne dass diese Parameter erneut eingegeben werden müssen. Es ermöglicht Ihnen, die Sicherheit von Generika mit fast zu halten dem gleichen Aufwand wie bei der Verwendung .

Ich denke, der Schlüssel zum Verständnis ist, dass Rohtypen (ohne <>) nicht wie generische Typen behandelt werden können. Wenn Sie einen Rohtyp deklarieren, erhalten Sie keinen der Vorteile und die Typprüfung von Generika. Sie müssen auch bedenken, dass Generika ein allgemeiner Bestandteil der Java-Sprache sind ... sie gelten nicht nur für die No-Arg-Konstruktoren von Collections!

ColinD
quelle
31
Die Abwärtskompatibilität ist großartig, aber bitte nicht auf Kosten der Komplexität. Warum konnte Java 7 nicht einfach den -compatibilityCompiler-Switch einführen , während bei Abwesenheit javacalle Raw-Typen verboten und nur streng generische Typen erzwungen werden? Dadurch werden unsere Codes weniger ausführlich.
Rosdi Kasim
3
@Rosdi: Einverstanden, es gibt keine Notwendigkeit für Rohtypen in neuem Code. Ich würde es jedoch sehr bevorzugen, die Java-Versionsnummer in die Quelldatei aufzunehmen (anstatt (falsch) die Befehlszeile zu verwenden), siehe meine Antwort.
Maaartinus
37
Ich persönlich mag die Verwendung von Diamanten nicht, es sei denn, Sie definieren UND instanziieren auf derselben Linie. List<String> strings = new List<>()ist in Ordnung, aber wenn Sie definieren private List<String> my list;und dann die Hälfte der Seite, mit der Sie instanziieren my_list = new List<>(), dann ist es nicht cool! Was enthält meine Liste nochmal? Oh, lass mich nach der Definition suchen. Plötzlich verabschiedet sich der Vorteil der Diamantverknüpfung.
Rmirabelle
11
@rmirabelle Wie unterscheidet sich das von : my_list = getTheList()? Es gibt mehrere bessere Möglichkeiten, um mit dieser Art von Problemen umzugehen: 1. Verwenden Sie eine IDE, die Ihnen beim Bewegen des Mauszeigers die Variablentypen anzeigt. 2. Verwenden Sie aussagekräftigere Variablennamen, z. B. private List<String> strings3. Teilen Sie die Deklaration und Initialisierung von Variablen nicht auf, es sei denn, Sie müssen dies wirklich tun.
Natix
1
@Morfidon: Ja, es für Java 8 nach wie vor gültig ist bin ich ziemlich sicher , dass Sie sollten immer Warnungen werden.
ColinD
36

Ihr Verständnis ist leicht fehlerhaft. Der Diamantoperator ist eine nette Funktion, da Sie sich nicht wiederholen müssen. Es ist sinnvoll, den Typ einmal zu definieren, wenn Sie den Typ deklarieren, aber es ist einfach nicht sinnvoll, ihn auf der rechten Seite erneut zu definieren. Das DRY-Prinzip.

Erklären Sie nun alle Probleme beim Definieren von Typen. Sie haben Recht, dass der Typ zur Laufzeit entfernt wird, aber sobald Sie etwas aus einer Liste mit Typdefinition abrufen möchten, erhalten Sie es als den Typ zurück, den Sie beim Deklarieren der Liste definiert haben, da sonst alle spezifischen Funktionen verloren gehen und nur die Objektfunktionen, außer wenn Sie das abgerufene Objekt in den ursprünglichen Typ umwandeln, was manchmal sehr schwierig sein kann und zu einer ClassCastException führt.

Wenn Sie verwenden List<String> list = new LinkedList(), erhalten Sie Warnungen vom Rohtyp.

Octavian A. Damiean
quelle
8
List<String> list = new LinkedList()ist der richtige Code. Du weißt das und ich weiß das auch. Und die Frage (so wie ich es verstehe) ist: Warum versteht nur der Java-Compiler nicht, dass dieser Code ziemlich sicher ist?
Roman
22
@ Roman: List<String> list = new LinkedList()ist nicht der richtige Code. Klar, es wäre schön, wenn es so wäre! Und es hätte wahrscheinlich sein können, wenn Java von Anfang an Generika hatte und sich nicht mit der Abwärtskompatibilität von generischen Typen befassen musste, die früher nicht generisch waren, aber das tut es.
ColinD
6
@ColinD Java muss sich wirklich nicht mit der Abwärtskompatibilität in jeder einzelnen Zeile befassen . In jeder Java-Quelldatei, die Generika verwendet, sollten die alten nicht generischen Typen verboten sein (Sie können sie immer verwenden, <?>wenn Sie eine Schnittstelle zu Legacy-Code herstellen), und der nutzlose Diamantoperator sollte nicht vorhanden sein.
Maaartinus
16

Diese Zeile verursacht die Warnung [nicht markiert]:

List<String> list = new LinkedList();

Die Frage ändert sich also: Warum wird die [nicht aktivierte] Warnung nicht automatisch nur für den Fall unterdrückt, dass eine neue Sammlung erstellt wird?

Ich denke, es wäre viel schwieriger, eine <>Funktion hinzuzufügen .

UPD : Ich denke auch, dass es ein Chaos geben würde, wenn es legal wäre, rohe Typen "nur für ein paar Dinge" zu verwenden.

römisch
quelle
13

Theoretisch können Sie mit dem Diamantoperator kompakteren (und lesbareren) Code schreiben, indem Sie Argumente vom Typ "wiederholt" speichern. In der Praxis sind es nur zwei verwirrende Zeichen, die Ihnen nichts geben. Warum?

  1. Kein vernünftiger Programmierer verwendet Rohtypen in neuem Code. Der Compiler könnte also einfach davon ausgehen, dass er durch Schreiben von Typargumenten darauf schließen soll.
  2. Der Diamantoperator liefert keine Typinformationen, er sagt nur dem Compiler, "es wird in Ordnung sein". Wenn Sie es also weglassen, können Sie keinen Schaden anrichten. An jedem Ort, an dem der Diamantoperator legal ist, kann er vom Compiler "abgeleitet" werden.

Meiner Meinung nach wäre es nützlicher, eine Quelle klar und einfach als Java 7 zu markieren, als solche seltsamen Dinge zu erfinden. In so markiertem Code könnten Rohtypen verboten werden, ohne etwas zu verlieren.

Übrigens denke ich nicht, dass dies mit einem Kompilierungsschalter erfolgen sollte. Die Java-Version einer Programmdatei ist ein Attribut der Datei, überhaupt keine Option. Verwenden Sie etwas so Triviales wie

package 7 com.example;

könnte es klar machen (Sie bevorzugen vielleicht etwas Anspruchsvolleres, einschließlich eines oder mehrerer ausgefallener Schlüsselwörter). Es würde sogar ermöglichen, Quellen, die für verschiedene Java-Versionen geschrieben wurden, problemlos zusammen zu kompilieren. Es würde die Einführung neuer Schlüsselwörter (z. B. "Modul") oder das Löschen einiger veralteter Funktionen (mehrere nicht öffentliche nicht verschachtelte Klassen in einer einzelnen Datei oder was auch immer) ermöglichen, ohne die Kompatibilität zu verlieren.

Maaartinus
quelle
2
Haben Sie den Unterschied zwischen new ArrayList(anotherList)und berücksichtigt new ArrayList<>(anotherList)(insbesondere, wenn es a zugewiesen ist List<String>und a anotherListist List<Integer>)?
Paul Bellora
@ Paul Bellora: Nein. Überraschenderweise kompilieren beide für mich. Der mit den Diamanten gab sogar keine Warnung. Ich sehe darin jedoch keinen Sinn. Können Sie das näher erläutern?
Maaartinus
Entschuldigung, ich habe es nicht sehr gut erklärt. Sehen Sie den Unterschied zwischen diesen beiden Beispielen: ideone.com/uyHagh und ideone.com/ANkg3T Ich möchte nur darauf hinweisen, dass es wichtig ist, den Diamantoperator anstelle eines Rohtyps zu verwenden, zumindest wenn Argumente mit generischen Grenzen übergeben werden in.
Paul Bellora
Eigentlich hatte ich mir nicht die Zeit genommen, ColinDs Antwort zu lesen - er zitiert so ziemlich das Gleiche.
Paul Bellora
2
Also, wenn wir im Begriff sind , eine neue Syntax für rohe Typen einführen, für die wenige Stellen , wo es wirklich nötig ist, warum nicht so etwas wie verwenden new @RawType List(). Das ist bereits eine gültige Java 8-Syntax, und Typanmerkungen ermöglichen die Verwendung an jedem Ort, an dem sie benötigt werden, z @RawType List = (@RawType List) genericMethod();. In Anbetracht der Tatsache, dass Raw-Typen derzeit eine Compiler-Warnung erstellen, sofern keine entsprechende @SuppressWarningsplatziert wurde, @RawTypewäre dies ein angemessener Ersatz, und eine subtilere Syntax ist nicht erforderlich.
Holger
8

Wenn du schreibst List<String> list = new LinkedList(); , erzeugt der Compiler eine "ungeprüfte" Warnung. Sie können es ignorieren, aber wenn Sie diese Warnungen früher ignoriert haben, können Sie auch eine Warnung übersehen, die Sie über ein echtes Sicherheitsproblem informiert.

Es ist daher besser, einen Code zu schreiben, der keine zusätzlichen Warnungen generiert, und mit dem Diamantoperator können Sie dies auf bequeme Weise ohne unnötige Wiederholung tun.

axtavt
quelle
4

Alle in den anderen Antworten genannten sind gültig, aber die Anwendungsfälle sind meiner Meinung nach nicht vollständig gültig. Wenn man Guava und insbesondere die sammlungsbezogenen Dinge auscheckt, wurde dasselbe mit statischen Methoden gemacht. ZB Lists.newArrayList () , mit dem Sie schreiben können

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

oder mit statischem Import

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

Guave hat andere sehr mächtige Funktionen wie diese und ich kann mir eigentlich nicht viele Verwendungsmöglichkeiten für das <> vorstellen.

Es wäre nützlicher gewesen, wenn das Verhalten des Diamantoperators als Standard festgelegt worden wäre, dh der Typ wird von der linken Seite des Ausdrucks abgeleitet, oder wenn der Typ der linken Seite von der rechten Seite abgeleitet wurde. Letzteres passiert in Scala.

allprog
quelle
3

Der Punkt für den Diamantoperator besteht einfach darin, die Eingabe von Code beim Deklarieren generischer Typen zu reduzieren. Es hat keinerlei Auswirkungen auf die Laufzeit.

Der einzige Unterschied, wenn Sie in Java 5 und 6 angeben,

List<String> list = new ArrayList();

ist , dass Sie angeben müssen , @SuppressWarnings("unchecked")an die list(sonst erhalten Sie eine ungeprüfte Guss Warnung). Mein Verständnis ist, dass der Diamantbetreiber versucht, die Entwicklung zu vereinfachen. Es hat überhaupt nichts mit der Laufzeitausführung von Generika zu tun.

Buhake Sindi
quelle
Sie müssen diese Anmerkung nicht einmal verwenden. Zumindest in Eclipse können Sie dem Compiler nur sagen, dass er Sie nicht davor warnen soll, und es geht Ihnen gut ...
Xerus
Es ist besser, die Anmerkung zu haben. Nicht jeder Entwickler verwendet hier draußen Eclipse.
Buhake Sindi