Dies war eine Frage, die ich kürzlich in einem Interview gestellt hatte, als etwas, das der Kandidat der Java-Sprache hinzufügen wollte. Es wird allgemein als Schmerz bezeichnet, dass Java keine generischen Generika hat, aber wenn der Kandidat dazu gedrängt wurde, konnte er mir nicht sagen, was er hätte erreichen können, wenn sie dort gewesen wären.
Da in Java Rohtypen zulässig sind (und unsichere Überprüfungen), ist es offensichtlich möglich, Generika zu untergraben und am Ende eine zu erhalten List<Integer>
, die (zum Beispiel) tatsächlich String
s enthält . Dies könnte eindeutig unmöglich gemacht werden, wenn die Typinformationen geändert würden. aber es muss noch mehr geben !
Könnten die Leute Beispiele für Dinge posten, die sie wirklich tun möchten, wenn reifizierte Generika verfügbar sind? Ich meine, natürlich könnten Sie zur List
Laufzeit den Typ eines bekommen - aber was würden Sie damit machen?
public <T> void foo(List<T> l) {
if (l.getGenericType() == Integer.class) {
//yeah baby! err, what now?
BEARBEITEN : Eine schnelle Aktualisierung, da die Antworten hauptsächlich über die Notwendigkeit besorgt zu sein scheinen, einen Class
als Parameter zu übergeben (zum Beispiel EnumSet.noneOf(TimeUnit.class)
). Ich habe mehr nach etwas gesucht, bei dem dies einfach nicht möglich ist . Beispielsweise:
List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();
if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
l1.addAll(l2); //why on earth would I be doing this anyway?
quelle
findClass()
die Parametrisierung ignoriert werden müsste, dies aberdefineClass()
nicht konnte). Und wie wir wissen, stehen die Powers That Be für die Abwärtskompatibilität an erster Stelle.Antworten:
Von den wenigen Malen, in denen ich auf dieses "Bedürfnis" gestoßen bin, läuft es letztendlich auf dieses Konstrukt hinaus:
Dies funktioniert in C # unter der Annahme, dass
T
ein Standardkonstruktor vorhanden ist . Sie können sogar den Laufzeit-Typ vontypeof(T)
und die Konstruktoren von abrufenType.GetConstructor()
.Die übliche Java-Lösung wäre, das
Class<T>
Argument as zu übergeben.(Es muss nicht unbedingt als Konstruktorargument übergeben werden, da ein Methodenargument ebenfalls in Ordnung ist. Das Obige ist nur ein Beispiel, auch das
try-catch
wird der Kürze halber weggelassen.)Für alle anderen generischen Typkonstrukte kann der tatsächliche Typ mit ein wenig Reflexion leicht aufgelöst werden. Die folgenden Fragen und Antworten veranschaulichen die Anwendungsfälle und Möglichkeiten:
quelle
T.class
oder abrufen könnenT.getClass()
, sodass Sie auf alle Felder, Konstruktoren und Methoden zugreifen können. Es macht auch das Bauen unmöglich.Das, was mich am häufigsten beißt, ist die Unfähigkeit, den Mehrfachversand über mehrere generische Typen hinweg zu nutzen. Folgendes ist nicht möglich und es gibt viele Fälle, in denen dies die beste Lösung wäre:
quelle
public void my_method(List input) {}
. Ich bin jedoch nie auf dieses Bedürfnis gestoßen, einfach weil sie nicht den gleichen Namen haben würden. Wenn sie den gleichen Namen haben, würde ich fragen, obpublic <T extends Object> void my_method(List<T> input) {}
das keine bessere Idee ist.myStringsMethod(List<String> input)
undmyIntegersMethod(List<Integer> input)
selbst dann zu bevorzugen, wenn eine Überladung für einen solchen Fall in Java möglich wäre.<algorithm>
in C ++ erhalten.Typensicherheit fällt mir ein. Das Downcasting auf einen parametrisierten Typ ist ohne reifizierte Generika immer unsicher:
Außerdem würden Abstraktionen weniger auslaufen - zumindest diejenigen, die an Laufzeitinformationen zu ihren Typparametern interessiert sein könnten. Wenn Sie heute Laufzeitinformationen über den Typ eines der generischen Parameter benötigen, müssen Sie diese ebenfalls weitergeben
Class
. Auf diese Weise hängt Ihre externe Schnittstelle von Ihrer Implementierung ab (ob Sie RTTI für Ihre Parameter verwenden oder nicht).quelle
ParametrizedList
die die Daten in den Prüftypen der Quellensammlung kopiert. Es ist ein bisschen wieCollections.checkedList
, kann aber zunächst mit einer Sammlung besiedelt werden.T.class.getAnnotation(MyAnnotation.class)
(woT
ist ein generischer Typ) hinzufügen können, ohne die externe Schnittstelle zu ändern.Sie können generische Arrays in Ihrem Code erstellen.
quelle
String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object();
Kompiliert gut in beiden Sprachen. Läuft notsofine.Dies ist eine alte Frage, es gibt eine Menge Antworten, aber ich denke, dass die vorhandenen Antworten falsch sind.
"reifiziert" bedeutet nur real und bedeutet normalerweise nur das Gegenteil von Typlöschung.
Das große Problem im Zusammenhang mit Java Generics:
void method(List<A> l)
undmethod(List<B> l)
. Dies ist auf das Löschen des Typs zurückzuführen, aber äußerst kleinlich.quelle
deserialize(thingy, List<Integer>.class)
Die Serialisierung wäre mit der Reifizierung einfacher. Was wir wollen würden, ist
Was wir tun müssen, ist
sieht hässlich aus und funktioniert funkig.
Es gibt auch Fälle, in denen es sehr hilfreich wäre, so etwas zu sagen
Diese Dinge beißen nicht oft, aber sie beißen, wenn sie passieren.
quelle
Meine Exposition gegenüber Java Geneircs ist sehr begrenzt, und abgesehen von den Punkten, die andere Antworten bereits erwähnt haben, gibt es ein Szenario, das in dem Buch Java Generics and Collections von Maurice Naftalin und Philip Walder erläutert wird, in dem die reifizierten Generika nützlich sind.
Da die Typen nicht überprüfbar sind, können keine parametrisierten Ausnahmen festgelegt werden.
Zum Beispiel ist die Erklärung des folgenden Formulars ungültig.
Dies liegt daran, dass die catch- Klausel prüft, ob die ausgelöste Ausnahme einem bestimmten Typ entspricht. Diese Prüfung entspricht der Prüfung durch den Instanztest. Da der Typ nicht überprüfbar ist, ist die obige Form der Anweisung ungültig.
Wenn der obige Code gültig wäre, wäre eine Ausnahmebehandlung auf die folgende Weise möglich gewesen:
Das Buch erwähnt auch, dass wenn Java-Generika ähnlich wie C ++ - Vorlagen definiert werden (Erweiterung), dies zu einer effizienteren Implementierung führen kann, da dies mehr Möglichkeiten zur Optimierung bietet. Bietet aber keine Erklärung mehr als diese, so dass jede Erklärung (Hinweise) von den sachkundigen Leuten hilfreich wäre.
quelle
Arrays würden mit Generika wahrscheinlich viel besser spielen, wenn sie reifiziert würden.
quelle
List<String>
ist nicht einList<Object>
.Ich habe einen Wrapper, der eine JDBC-Ergebnismenge als Iterator darstellt (dies bedeutet, dass ich von einer Datenbank stammende Operationen durch Abhängigkeitsinjektion viel einfacher testen kann).
Die API sieht so aus, als ob
Iterator<T>
T ein Typ ist, der nur mit Zeichenfolgen im Konstruktor erstellt werden kann. Der Iterator überprüft dann die von der SQL-Abfrage zurückgegebenen Zeichenfolgen und versucht, sie einem Konstruktor vom Typ T zuzuordnen.Bei der aktuellen Implementierung von Generika muss ich auch die Klasse der Objekte übergeben, die ich aus meiner Ergebnismenge erstellen werde. Wenn ich richtig verstehe, wenn Generika reifiziert würden, könnte ich einfach T.getClass () aufrufen, um seine Konstruktoren zu erhalten, und dann nicht das Ergebnis von Class.newInstance () umwandeln müssen, was weitaus ordentlicher wäre.
Grundsätzlich denke ich, dass es das Schreiben von APIs (im Gegensatz zum Schreiben einer Anwendung) einfacher macht, da Sie viel mehr aus Objekten ableiten können und daher weniger Konfiguration erforderlich ist ... Ich habe die Auswirkungen von Anmerkungen erst zu schätzen gewusst, als ich sah, dass sie in Dingen wie spring oder xstream anstelle von Unmengen von Konfigurationen verwendet wurden.
quelle
Eine schöne Sache wäre, das Boxen für primitive (Wert-) Typen zu vermeiden. Dies hängt etwas mit der Array-Beschwerde zusammen, die andere erhoben haben, und in Fällen, in denen die Speichernutzung eingeschränkt ist, kann dies tatsächlich einen signifikanten Unterschied bewirken.
Es gibt auch verschiedene Arten von Problemen beim Schreiben eines Frameworks, bei denen es wichtig ist, über den parametrisierten Typ nachdenken zu können. Natürlich kann dies umgangen werden, indem ein Klassenobjekt zur Laufzeit übergeben wird. Dies verdeckt jedoch die API und belastet den Benutzer des Frameworks zusätzlich.
quelle
new T[]
läuft im Wesentlichen darauf hinaus, in der Lage zu sein, dort zu tun, wo T vom primitiven Typ ist!Es ist nicht so, dass Sie etwas Außergewöhnliches erreichen würden. Es wird einfach einfacher zu verstehen sein. Das Löschen von Texten scheint für Anfänger eine schwierige Zeit zu sein, und es erfordert letztendlich Verständnis für die Funktionsweise des Compilers.
Meiner Meinung nach sind Generika einfach ein Extra , das viel redundantes Casting spart.
quelle
Etwas, das alle Antworten hier übersehen haben und das mir ständig Kopfschmerzen bereitet, ist, dass Sie eine generische Schnittstelle nicht zweimal erben können, da die Typen gelöscht werden. Dies kann ein Problem sein, wenn Sie feinkörnige Schnittstellen erstellen möchten.
quelle
Hier ist eine, die mich heute erwischt hat: Wenn Sie eine Methode schreiben, die eine varargs Liste generischer Elemente akzeptiert, können Anrufer ohne Bestätigung denken, dass sie typsicher sind, aber versehentlich einen alten Rohstoff übergeben und Ihre Methode in die Luft jagen.
Scheint unwahrscheinlich, dass das passieren würde? ... Sicher, bis ... Sie Class als Datentyp verwenden. Zu diesem Zeitpunkt sendet Ihnen Ihr Anrufer gerne viele Klassenobjekte, aber ein einfacher Tippfehler sendet Ihnen Klassenobjekte, die sich nicht an T halten, und Katastrophenfälle.
(NB: Ich habe hier vielleicht einen Fehler gemacht, aber beim Googeln um "Generika-Varargs" scheint das oben Genannte genau das zu sein, was Sie erwarten würden. Das, was dies zu einem praktischen Problem macht, ist die Verwendung von Klasse, denke ich - Anrufer scheinen weniger vorsichtig sein :()
Zum Beispiel verwende ich ein Paradigma, das Klassenobjekte als Schlüssel in Karten verwendet (es ist komplexer als eine einfache Karte - aber konzeptionell ist das der Fall).
zB funktioniert dies hervorragend in Java Generics (triviales Beispiel):
zB ohne Reifizierung in Java Generics akzeptiert dieses JEDES "Class" -Objekt. Und es ist nur eine winzige Erweiterung des vorherigen Codes:
Die oben genannten Methoden müssen in einem einzelnen Projekt tausende Male ausgeschrieben werden - damit die Wahrscheinlichkeit menschlicher Fehler hoch wird. Das Debuggen von Fehlern erweist sich als "nicht lustig". Ich versuche gerade, eine Alternative zu finden, habe aber nicht viel Hoffnung.
quelle