Was ist los mit Javas Generika? [geschlossen]

49

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?

Michael K
quelle
1
Siehe auch stackoverflow.com/questions/31693/…
Bill Michell
wie Clinton sagte Begin (die „iBatis guy“), „sie funktionieren nicht ...“
gnat

Antworten:

54

Die generische Implementierung von Java verwendet die Typlöschung . Dies bedeutet, dass Ihre stark typisierten generischen Auflistungen Objectzur 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.

ChaosPandion
quelle
4
Nur um hinzuzufügen, dass das Abrufen allgemeiner Informationen zur Laufzeit aufgrund der TypeLiteral Typlöschung
Jeremy Heiler,
43
Das heißt,new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
Notiz an sich selbst - denke an einen Namen
9
@Job nein tut es nicht. Und C ++ verwendet einen völlig anderen Ansatz für die Metaprogrammierung, sogenannte Templates. Es verwendet Duck-Typing und Sie können nützliche Dinge tun, wie 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, die addzwei Dinge sind, egal was diese Dinge sind, sie müssen nur eine Methode add()mit einem Parameter haben.
Trinidad
7
@Job es ist in der Tat Code generiert. Templates sollen den C-Präprozessor ersetzen und sollten dazu in der Lage sein, einige sehr clevere Tricks zu machen und sind für sich genommen eine Turing-vollständige Sprache. Java-Generika sind nur Zucker für typsichere Container, und C # -Generika sind besser, aber immer noch das C ++ - Template eines schlechten Mannes.
Trinidad
3
@Job AFAIK, Java-Generika generieren keine Klasse für jeden neuen generischen Typ, sondern fügen nur Typecasts zu dem Code hinzu, der generische Methoden verwendet, sodass es sich nicht um eine Metaprogrammierung von IMO handelt. C # -Vorlagen generieren für jeden generischen Typ neuen Code. Wenn Sie also List <int> und List <double> in der Welt von C # verwenden, wird für jeden Code generiert. Im Vergleich zu Vorlagen müssen C # -Generiker immer noch wissen, welchen Typ sie füttern können. Sie können das einfache AddBeispiel, das ich angegeben habe, nicht implementieren , ohne vorher zu wissen, auf welche Klassen sie angewendet werden können, was ein Nachteil ist.
Trinidad
26

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:

  • Die reflexionsbezogenen Teile der Klassenbibliothek können nicht die vollständigen Typinformationen verfügbar machen, die in Java-the-language verfügbar waren
  • einige Leistungseinbußen sind damit verbunden

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.

Roman Starkov
quelle
14
Die JVM muss keine Generika unterstützen. Dies entspricht der Aussage, dass C ++ keine Vorlagen hat, da der ix86-Realcomputer keine Vorlagen unterstützt, was falsch ist. Das Problem ist der Ansatz, mit dem der Compiler generische Typen implementiert. Und wieder, C ++ - Templates verursachen zur Laufzeit keine Einbußen, es werden überhaupt keine Überlegungen angestellt. Ich denke, der einzige Grund, warum dies in Java erforderlich wäre, ist nur ein schlechtes Sprachdesign.
Trinidad
2
@ Trinidad, aber ich habe nicht gesagt, Java hat keine Generika, also sehe ich nicht, wie es das gleiche ist. Und ja, wird die JVM nicht brauchen , sie zu unterstützen, genau wie C ++ nicht benötigen Sie den Code zu optimieren. Aber nicht zu optimieren würde sicherlich als "etwas Falsches" gelten.
Roman Starkov
3
Ich habe argumentiert, dass die JVM Generika nicht direkt unterstützen muss, damit sie reflektiert werden können. Andere haben es getan, damit es getan werden kann. Das Problem ist, dass Java keine neuen Klassen aus Generika generiert, die keine Reification-Sache sind.
Trinidad
4
@Trinidad: Unter der Annahme, dass der Compiler genügend Zeit und Speicher hat, können in C ++ Vorlagen verwendet werden, um alles zu tun, was mit Informationen möglich ist, die zur Kompilierungszeit verfügbar sind (da die Kompilierung von Vorlagen Turing-complete ist) Es ist nicht möglich, Vorlagen mit Informationen zu erstellen, die vor dem Ausführen des Programms nicht verfügbar sind. Im Gegensatz dazu ist es in .net für ein Programm möglich, Typen basierend auf Eingaben zu erstellen. Es wäre ziemlich einfach, ein Programm zu schreiben, in dem die Anzahl der verschiedenen Typen, die erzeugt werden könnten, die Anzahl der Elektronen im Universum übersteigt.
Supercat
2
@ Trinidad: Offensichtlich kann keine einzelne Ausführung des Programms alle diese Typen erzeugen (oder in der Tat alles, was über einen infinitesimalen Bruchteil von ihnen hinausgeht), aber der springende Punkt ist, dass dies nicht der Fall sein muss. Es müssen nur Typen erstellt werden, die tatsächlich verwendet werden. Man könnte ein Programm haben, das irgendeine beliebige Nummer akzeptiert (zB 8675309) und daraus einen für diese Nummer eindeutigen Typ erzeugt (zB Z8<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.
Supercat
13

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>zu List<File>. Der Compiler gibt Ihnen Warnungen aus, aber er warnt Sie auch, wenn Sie eine ArrayList<String>Zuordnung zu einer ObjectReferenz vornehmen und diese dann in eine umwandeln List<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>)und void 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.

Tom Hawtin - Tackline
quelle
"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." Wäre es nicht so? Wie würde es ohne Reification funktionieren? Müsste die JVM nicht zur Laufzeit wissen, welche Art von Menge Sie haben, um zu wissen, welche Methode aufzurufen ist?
MatrixFrog
2
@ MatrixFrog Nein. Wie ich bereits sagte, ist das Überladen ein Problem bei der Kompilierung. Der Compiler verwendet den statischen Typ des Ausdrucks, um eine bestimmte Überladung auszuwählen. Dies ist dann die Methode, die in die Klassendatei geschrieben wird. Wenn Sie haben Object cs = new char[] { 'H', 'i' }; System.out.println(cs);, bekommen Sie Unsinn gedruckt. Ändern Sie die Art von cszu char[]und Sie erhalten Hi.
Tom Hawtin - Tackline
10

Java-Generika sind zum Kotzen, weil Sie Folgendes nicht tun können:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

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.

davidk01
quelle
Sie müssen nicht jede Klasse schlachten. Sie müssen nur eine geeignete Factory-Klasse hinzufügen und in AsyncAdapter einfügen. (Und im Grunde geht es hier nicht um Generika, sondern um die Tatsache, dass Konstruktoren in Java nicht vererbt werden).
Peter Taylor
13
@PeterTaylor: Eine geeignete Werksklasse wirft einfach die gesamte Kesselplatte in ein anderes, unsichtbares Chaos und löst das Problem immer noch nicht. Jedes Mal, wenn ich eine neue Instanz von instanziieren möchte, Parsermuss 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.
Davidk01
2
Manchmal ähnlich in C ++, wo Sie die Adresse eines Konstruktors nicht übergeben können, wenn Sie Templates + Virtuals verwenden - Sie können stattdessen einfach einen Factory-Funktor verwenden.
Mark K Cowan
Java ist zum Kotzen, weil man vor einem Methodennamen nicht "proptected" schreiben kann? Du armer. Halten Sie sich an dynamische Sprachen. Und seien Sie besonders vorsichtig mit Haskell.
fdreger
5
  • 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.

Steve B.
quelle
Ich ärgere mich, wenn ich eine Warnung für die Verwendung HashMapanstelle von bekomme HashMap<String, String>.
Michael K
1
@ Michael, aber das sollte eigentlich mit Generika verwendet werden ...
Alternative
Das Array-Problem betrifft die Verifizierbarkeit. Eine Erklärung finden Sie im Java Generics-Buch (Alligator).
ncmathsadist
5

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:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

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 der ArrayList<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.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

Angenommen, wir wollten, dass eine generische Factory eine Instanz von erstellt, MySpecialObjectda 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.

Chubbsondubs
quelle
1

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

RalphChapin
quelle
Nizza schimpfen. Es gibt jedoch viele Bibliotheken / Frameworks, die ohne Generika existieren könnten, z. B. Guice, Gson, Hibernate. Und Generika sind nicht so schwer, wenn man sich erst einmal daran gewöhnt hat. Das Eingeben und Lesen der Typargumente ist eine echte PITA. Hier hilft val , wenn Sie verwenden können.
Maaartinus
1
@maaartinus: Schrieb das vor einer Weile. Auch OP hat nach "Fragen" gefragt. Ich finde Generika äußerst nützlich und mag sie sehr - viel mehr als damals. Allerdings , wenn Sie Ihre eigene Sammlung zu schreiben, sind sie sehr schwer zu lernen. Und ihre Nützlichkeit lässt nach, wenn der Typ Ihrer Sammlung zur Laufzeit bestimmt wird - wenn die Sammlung über eine Methode verfügt , die Ihnen die Klasse ihrer Einträge angibt . An diesem Punkt funktionieren Generika nicht und Ihre IDE wird Hunderte bedeutungsloser Warnungen ausgeben. 99,99% der Java-Programmierung beinhalten dies nicht. Aber ich würde nur hatte eine Menge Ärger mit diesem , wenn ich die oben geschrieben habe.
RalphChapin
Als Java- und C # -Entwickler frage ich mich, warum Sie Java-Sammlungen bevorzugen.
Ivaylo Slavov
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 ...
Mason Wheeler,