Ich habe auf dieser Site mehrere Posts gesehen, die Javas Implementierung von Generika verurteilen. Jetzt kann ich ehrlich sagen, dass ich keine Probleme damit hatte, sie zu benutzen. Ich habe jedoch nicht versucht, selbst eine generische Klasse zu erstellen. Was sind Ihre Probleme mit Javas generischem Support?
49
Antworten:
Die generische Implementierung von Java verwendet die Typlöschung . Dies bedeutet, dass Ihre stark typisierten generischen Auflistungen
Object
zur Laufzeit tatsächlich vom Typ sind . Dies hat einige Leistungsaspekte, da primitive Typen beim Hinzufügen zu einer generischen Auflistung in ein Kästchen gesetzt werden müssen. Natürlich überwiegen die Vorteile der Typkorrektheit bei der Kompilierung die allgemeine Dummheit beim Löschen von Typen und der obsessive Fokus auf Rückwärtskompatibilität.quelle
TypeLiteral
Typlöschungnew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
template<T> T add(T v1, T v2) { return v1->add(v2); }
und dort haben Sie eine wirklich generische Möglichkeit, eine Funktion zu erstellen, dieadd
zwei Dinge sind, egal was diese Dinge sind, sie müssen nur eine Methodeadd()
mit einem Parameter haben.Add
Beispiel, das ich angegeben habe, nicht implementieren , ohne vorher zu wissen, auf welche Klassen sie angewendet werden können, was ein Nachteil ist.Beachten Sie, dass sich die bereits gegebenen Antworten auf die Kombination von Java-the-Language, JVM und der Java-Klassenbibliothek konzentrieren.
In Bezug auf Java-the-language ist an Javas Generika nichts auszusetzen. Wie in C # im Vergleich zu Java-Generika beschrieben , sind Java-Generika auf Sprachniveau 1 ziemlich gut .
Suboptimal ist, dass die JVM Generika nicht direkt unterstützt, was mehrere schwerwiegende Konsequenzen hat:
Ich nehme an, dass die Unterscheidung, die ich mache, umständlich ist, da Java-the-language mit JVM als Ziel und Java Class Library als Kernbibliothek allgegenwärtig kompiliert wird.
1 Mit der möglichen Ausnahme von Platzhaltern, von denen angenommen wird, dass sie die Typinferenz im allgemeinen Fall unentscheidbar machen. Dies ist ein wesentlicher Unterschied zwischen C # - und Java-Generika, der überhaupt nicht oft erwähnt wird. Danke, Antimon.
quelle
8675309
) und daraus einen für diese Nummer eindeutigen Typ erzeugt (zBZ8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>
), der andere Mitglieder als jeder andere Typ hat. In C ++ müssten alle Typen, die aus einer Eingabe generiert werden könnten, zur Kompilierungszeit erstellt werden.Die übliche Kritik ist das Fehlen einer Bestätigung. Das heißt, Objekte enthalten zur Laufzeit keine Informationen über ihre generischen Argumente (obwohl die Informationen immer noch in Feldern, Methoden, Konstruktoren und erweiterten Klassen und Schnittstellen vorhanden sind). Dies bedeutet , dass Sie könnten werfen, sagen
ArrayList<String>
zuList<File>
. Der Compiler gibt Ihnen Warnungen aus, aber er warnt Sie auch, wenn Sie eineArrayList<String>
Zuordnung zu einerObject
Referenz vornehmen und diese dann in eine umwandelnList<String>
. Die Vorteile sind, dass Sie bei Generika das Casting wahrscheinlich nicht durchführen sollten, da die Leistung ohne die unnötigen Daten und natürlich die Abwärtskompatibilität besser ist.Einige Leute beschweren sich, dass Sie nicht auf der Basis von generischen Argumenten (
void fn(Set<String>)
undvoid fn(Set<File>)
) überladen können . Sie müssen stattdessen bessere Methodennamen verwenden. Beachten Sie, dass für diese Überladung keine erneute Überprüfung erforderlich ist, da es sich bei der Überladung um ein statisches Problem bei der Kompilierung handelt.Primitive Typen funktionieren nicht mit Generika.
Platzhalter und Schranken sind recht kompliziert. Sie sind sehr nützlich. Wenn Java Unveränderlichkeit und Tell-Don't-Ask-Interfaces vorziehen würde, wären deklarationsseitige Generika besser geeignet als anwendungsseitige.
quelle
Object cs = new char[] { 'H', 'i' }; System.out.println(cs);
, bekommen Sie Unsinn gedruckt. Ändern Sie die Art voncs
zuchar[]
und Sie erhaltenHi
.Java-Generika sind zum Kotzen, weil Sie Folgendes nicht tun können:
Sie müssten nicht jede Klasse im obigen Code abtöten, damit sie ordnungsgemäß funktioniert, wenn es sich bei den generischen Klassenparametern tatsächlich um generische handelt.
quelle
Parser
muss der Factory-Code noch komplexer werden. Wäre dagegen "generisch" wirklich generisch, wären Fabriken und der ganze andere Unsinn, der unter dem Namen Design Patterns bekannt ist, nicht erforderlich. Das ist einer der vielen Gründe, warum ich lieber in dynamischen Sprachen arbeite.Compiler-Warnungen für fehlende generische Parameter, die keinen Zweck erfüllen, machen die Sprache sinnlos wortreich, z
public String getName(Class<?> klazz){ return klazz.getName();}
Generika spielen nicht gut mit Arrays
Verlorene Typinformationen machen die Reflexion zu einem Durcheinander von Guss und Klebeband.
quelle
HashMap
anstelle von bekommeHashMap<String, String>
.Ich denke, andere Antworten haben dies zu einem gewissen Grad gesagt, aber nicht sehr deutlich. Eines der Probleme mit Generika besteht darin, die generischen Typen während des Reflektionsprozesses zu verlieren. Also zum Beispiel:
Leider können Ihnen die zurückgegebenen Typen nicht sagen, dass die generischen Typen von arr String sind. Es ist ein subtiler Unterschied, aber es ist wichtig. Da arr zur Laufzeit erstellt wird, werden die generischen Typen zur Laufzeit gelöscht, sodass Sie das nicht herausfinden können. Wie einige gesagt haben,
ArrayList<Integer>
sieht es aus derArrayList<String>
Sicht der Reflexion genauso aus.Dies ist für den Benutzer von Generics möglicherweise nicht von Bedeutung. Nehmen wir jedoch an, wir wollten ein ausgefallenes Framework erstellen, das mithilfe von Reflektion herausfindet, wie der Benutzer die konkreten generischen Typen einer Instanz deklariert.
Angenommen, wir wollten, dass eine generische Factory eine Instanz von erstellt,
MySpecialObject
da dies der konkrete generische Typ ist, den wir für diese Instanz deklariert haben. Nun, die Factory-Klasse kann sich nicht selbst abfragen, um den für diese Instanz deklarierten konkreten Typ herauszufinden, da sie von Java gelöscht wurden.In .Net's Generics können Sie dies tun, weil das Objekt zur Laufzeit die generischen Typen kennt, weil der Compiler sie in die Binärdatei eingearbeitet hat. Mit Löschungen kann Java dies nicht tun.
quelle
Ich könnte ein paar gute Dinge über Generika sagen , aber das war nicht die Frage. Ich könnte mich beschweren, dass sie zur Laufzeit nicht verfügbar sind und nicht mit Arrays funktionieren, aber das wurde bereits erwähnt.
Ein großes psychologisches Ärgernis: Ich gerate gelegentlich in Situationen, in denen Generika nicht zum Funktionieren gebracht werden können. (Arrays sind das einfachste Beispiel.) Und ich kann nicht herausfinden, ob Generika den Job nicht machen können oder ob ich nur dumm bin. Ich hasse es, dass. So etwas wie Generika sollte immer funktionieren. Jedes Mal, wenn ich mit Java-the-language nicht tun kann, was ich will, weiß ich, dass ich das Problem bin, und ich weiß, dass ich es irgendwann schaffen werde, wenn ich einfach weiter pushe. Wenn ich mit Generika zu hartnäckig werde, kann ich viel Zeit verschwenden.
Das eigentliche Problem ist jedoch, dass Generika zu viel Komplexität für zu wenig Gewinn hinzufügen. In den einfachsten Fällen kann es mich davon abhalten, einen Apfel zu einer Liste mit Autos hinzuzufügen. Fein. Aber ohne Generika würde dieser Fehler eine ClassCastException zur Laufzeit sehr schnell auslösen und wenig Zeit verschwenden. Wenn ich ein Auto mit einem Kindersitz hinzufüge, in dem sich ein Kind befindet, muss ich beim Kompilieren warnen, dass die Liste nur für Autos mit Kindersitzen gilt, die Babyschimpansen enthalten? Eine Liste einfacher Objektinstanzen scheint eine gute Idee zu sein.
Generierter Code kann viele Wörter und Zeichen enthalten, viel zusätzlichen Platz beanspruchen und viel länger zum Lesen brauchen. Ich kann viel Zeit damit verbringen, den ganzen zusätzlichen Code zum Laufen zu bringen. Wenn es so ist, fühle ich mich wahnsinnig schlau. Ich habe auch mehrere Stunden oder mehr meiner eigenen Zeit verschwendet und ich muss mich fragen, ob irgendjemand sonst jemals in der Lage sein wird, den Code herauszufinden. Ich möchte es zur Wartung an Leute weitergeben können, die entweder weniger schlau sind als ich oder die weniger Zeit zum Verschwenden haben.
Auf der anderen Seite (da habe ich mich ein bisschen aufgewickelt und das Gefühl, dass ich ein wenig ausbalancieren muss) ist es schön, wenn man einfache Sammlungen und Karten verwendet, um sie hinzuzufügen, zu setzen und beim Schreiben überprüft zu bekommen, und normalerweise fügt es nicht viel hinzu Komplexität des Codes ( wenn jemand anders die Sammlung oder Karte schreibt) . Und Java kann das besser als C #. Es scheint, als würde keine der C # -Sammlungen, die ich verwenden möchte, jemals mit Generika umgehen. (Ich gebe zu, ich habe einen seltsamen Geschmack in Sammlungen.)
quelle
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.
Das hängt wirklich davon ab, wie viel Laufzeit zwischen dem Programmstart und dem Erreichen der fraglichen Codezeile liegt. Wenn ein Benutzer den Fehler meldet und Sie mehrere Minuten (oder im schlimmsten Fall Stunden oder sogar Tage) benötigen, um ihn zu reproduzieren, sieht die Überprüfung zur Kompilierungszeit immer besser aus ...