Ich habe gelegentlich gehört, dass Java mit Generika nicht richtig verstanden hat. (nächste Referenz hier )
Verzeihen Sie meine Unerfahrenheit, aber was hätte sie besser gemacht?
Ich habe gelegentlich gehört, dass Java mit Generika nicht richtig verstanden hat. (nächste Referenz hier )
Verzeihen Sie meine Unerfahrenheit, aber was hätte sie besser gemacht?
Antworten:
Schlecht:
List<byte>
wirklich durch ein byte[]
Beispiel unterstützt, und es ist kein Boxen erforderlich).Gut:
Das größte Problem ist, dass Java-Generika nur zur Kompilierungszeit verfügbar sind und Sie sie zur Laufzeit untergraben können. C # wird gelobt, weil es mehr Laufzeitprüfungen durchführt. In diesem Beitrag gibt es einige wirklich gute Diskussionen , die auf andere Diskussionen verweisen.
Class
Objekte weitergeben.
Das Hauptproblem ist, dass Java zur Laufzeit keine Generika hat. Es ist eine Funktion zur Kompilierungszeit.
Wenn Sie eine generische Klasse in Java erstellen, verwenden sie eine Methode namens "Type Erasure", um tatsächlich alle generischen Typen aus der Klasse zu entfernen und sie im Wesentlichen durch Object zu ersetzen. Die meilenhohe Version von Generika besteht darin, dass der Compiler einfach Casts in den angegebenen generischen Typ einfügt, wenn er im Methodenkörper angezeigt wird.
Dies hat viele Nachteile. Einer der größten, IMHO, ist, dass Sie keine Reflexion verwenden können, um einen generischen Typ zu untersuchen. Typen sind im Bytecode nicht generisch und können daher nicht als generische Typen überprüft werden.
Großartiger Überblick über die Unterschiede hier: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) führt zu einem sehr seltsamen Verhalten. Das beste Beispiel, an das ich denken kann, ist. Annehmen:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
deklarieren Sie dann zwei Variablen:
MyClass<T> m1 = ...
MyClass m2 = ...
Rufen Sie jetzt an getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Das zweite generische Typargument wird vom Compiler entfernt, da es sich um einen Rohtyp handelt (dh der parametrisierte Typ wird nicht angegeben), obwohl er nichts enthält mit dem parametrisierten Typ zu tun hat.
Ich werde auch meine Lieblingserklärung des JDK erwähnen:
public class Enum<T extends Enum<T>>
Abgesehen von Wildcarding (was eine gemischte Tasche ist) denke ich nur, dass die .Net-Generika besser sind.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
mag auf den ersten Blick seltsam / redundant erscheinen, ist aber tatsächlich ziemlich interessant, zumindest im Rahmen der Einschränkungen von Java / seinen Generika. Aufzählungen haben eine statische values()
Methode, die ein Array ihrer Elemente angibt, die als Aufzählung und nicht als Aufzählung eingegeben werden. Dieser Enum
Typ wird durch den generischen Parameter bestimmt, was bedeutet, dass Sie möchten Enum<T>
. Natürlich ist diese Eingabe nur im Kontext eines Aufzählungstyps sinnvoll, und alle Aufzählungen sind Unterklassen von Enum
, also möchten Sie Enum<T extends Enum>
. Java mag es jedoch nicht, Rohtypen mit Generika zu mischen, Enum<T extends Enum<T>>
um die Konsistenz zu gewährleisten.
Ich werde eine wirklich kontroverse Meinung abgeben. Generika erschweren die Sprache und den Code. Angenommen, ich habe eine Zuordnung, die eine Zeichenfolge einer Liste von Zeichenfolgen zuordnet. Früher konnte ich das einfach so erklären
Map someMap;
Jetzt muss ich es als deklarieren
Map<String, List<String>> someMap;
Und jedes Mal, wenn ich es in eine Methode übergebe, muss ich diese große lange Erklärung noch einmal wiederholen. Meiner Meinung nach lenkt all diese zusätzliche Eingabe den Entwickler ab und bringt ihn aus der "Zone". Wenn der Code mit viel Cruft gefüllt ist, ist es manchmal schwierig, später darauf zurückzukommen und schnell alle Cruft zu durchsuchen, um die wichtige Logik zu finden.
Java hat bereits einen schlechten Ruf als eine der ausführlichsten Sprachen, die allgemein verwendet werden, und Generika tragen nur zu diesem Problem bei.
Und was kaufen Sie wirklich für all diese zusätzliche Ausführlichkeit? Wie oft hatten Sie wirklich Probleme, wenn jemand eine Ganzzahl in eine Sammlung eingefügt hat, die Strings enthalten soll, oder wenn jemand versucht hat, einen String aus einer Sammlung von Ganzzahlen herauszuziehen? In meiner 10-jährigen Erfahrung beim Erstellen kommerzieller Java-Anwendungen war dies nie eine große Fehlerquelle. Ich bin mir also nicht sicher, was Sie für die zusätzliche Ausführlichkeit bekommen. Es kommt mir wirklich nur als extra bürokratisches Gepäck vor.
Jetzt werde ich wirklich kontrovers. Was ich als das größte Problem bei Sammlungen in Java 1.4 sehe, ist die Notwendigkeit, überall typisiert zu werden. Ich betrachte diese Typografien als zusätzliche, ausführliche Cruft, die viele der gleichen Probleme haben wie Generika. So kann ich zum Beispiel nicht einfach machen
List someList = someMap.get("some key");
Ich muss tun
List someList = (List) someMap.get("some key");
Der Grund ist natürlich, dass get () ein Objekt zurückgibt, das ein Supertyp von List ist. Die Zuordnung kann also nicht ohne Typografie erfolgen. Denken Sie noch einmal darüber nach, wie viel Ihnen diese Regel wirklich einbringt. Aus meiner Erfahrung nicht viel.
Ich denke, Java wäre viel besser dran gewesen, wenn 1) es keine Generika hinzugefügt hätte, sondern 2) stattdessen implizites Casting von einem Supertyp zu einem Subtyp erlaubt hätte. Lassen Sie zur Laufzeit falsche Casts abfangen. Dann hätte ich die Einfachheit des Definierens haben können
Map someMap;
und später tun
List someList = someMap.get("some key");
Die ganze Kruft wäre verschwunden, und ich glaube wirklich nicht, dass ich eine große neue Fehlerquelle in meinen Code einführen würde.
Ein weiterer Nebeneffekt der Kompilierungszeit und nicht der Laufzeit ist, dass Sie den Konstruktor des generischen Typs nicht aufrufen können. Sie können sie also nicht verwenden, um eine generische Factory zu implementieren ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Java - Generika sind für Korrektheit bei der Kompilierung überprüft und dann alle Typinformationen entfernt (der Vorgang wird als Typ Löschung . So generic List<Integer>
wird auf seinen reduziert werden rohe Art , nicht-genericList
, die Objekte beliebiger Klasse enthalten.
Dies führt dazu, dass zur Laufzeit beliebige Objekte in die Liste eingefügt werden können und es jetzt unmöglich ist zu sagen, welche Typen als generische Parameter verwendet wurden. Letzteres führt wiederum zu
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Ich wünschte, dies wäre ein Wiki, damit ich es anderen Leuten hinzufügen könnte ... aber ...
Probleme:
<
? Erweitert MyObject >
[], aber ich darf nicht)Generika, wie angegeben, funktionieren einfach nicht.
Dies kompiliert:
List<Integer> x = Collections.emptyList();
Dies ist jedoch ein Syntaxfehler:
foo(Collections.emptyList());
Wo foo definiert ist als:
void foo(List<Integer> x) { /* method body not important */ }
Ob ein Ausdruckstyp prüft, hängt also davon ab, ob er einer lokalen Variablen oder einem tatsächlichen Parameter eines Methodenaufrufs zugewiesen wird. Wie verrückt ist das?
Die Einführung von Generika in Java war eine schwierige Aufgabe, da die Architekten versuchten, Funktionalität, Benutzerfreundlichkeit und Abwärtskompatibilität mit Legacy-Code in Einklang zu bringen. Erwartungsgemäß mussten Kompromisse eingegangen werden.
Es gibt einige, die auch der Meinung sind, dass die Implementierung von Generika durch Java die Komplexität der Sprache auf ein inakzeptables Maß erhöht hat (siehe Ken Arnolds " Generics Considered Harmful "). Angelika Langers FAQs zu Generika geben eine ziemlich gute Vorstellung davon, wie kompliziert Dinge werden können.
Java erzwingt Generics nicht zur Laufzeit, sondern nur zur Kompilierungszeit.
Dies bedeutet, dass Sie interessante Dinge tun können, z. B. generische Sammlungen mit den falschen Typen versehen.
Java-Generika sind nur zur Kompilierungszeit und werden in nicht generischen Code kompiliert. In C # ist die tatsächlich kompilierte MSIL generisch. Dies hat enorme Auswirkungen auf die Leistung, da Java zur Laufzeit noch ausgeführt wird. Weitere Informationen finden Sie hier .
Wenn Sie Java Posse # 279 - Interview mit Joe Darcy und Alex Buckley hören , sprechen sie über dieses Problem. Das verweist auch auf einen Neal Gafter-Blogbeitrag mit dem Titel Reified Generics for Java , der besagt:
Viele Menschen sind mit den Einschränkungen, die durch die Implementierung von Generika in Java verursacht werden, nicht zufrieden. Insbesondere sind sie unglücklich darüber, dass generische Typparameter nicht geändert werden: Sie sind zur Laufzeit nicht verfügbar. Generika werden durch Löschen implementiert, wobei generische Typparameter einfach zur Laufzeit entfernt werden.
Dieser Blog-Beitrag verweist auf einen älteren Eintrag, Puzzling Through Erasure: Antwortabschnitt , in dem der Punkt zur Migrationskompatibilität in den Anforderungen hervorgehoben wurde.
Ziel war es, die Abwärtskompatibilität von Quell- und Objektcode sowie die Migrationskompatibilität zu gewährleisten.