Was ist das Konzept des Löschens in Generika in Java?

Was ist das Konzept des Löschens in Generika in Java?

Solutions Collecting From Web of "Was ist das Konzept des Löschens in Generika in Java?"

Es ist im Grunde die Art, wie Generika in Java mittels Compiler Tricks implementiert werden. Der kompilierte generische Code verwendet eigentlich nur java.lang.Object wo immer man über T (oder irgendeinen anderen Typparameter) spricht – und es gibt einige Metadaten, die dem Compiler sagen, dass es wirklich ein generischer Typ ist.

Wenn Sie etwas Code gegen einen generischen Typ oder eine Methode kompilieren, ermittelt der Compiler, was Sie wirklich meinen (dh was das Typargument für T ist) und überprüft zur Kompilierungszeit, dass Sie das Richtige tun, aber den ausgegebenen Code einfach wieder spricht in java.lang.Object – der Compiler erzeugt bei Bedarf zusätzliche Umwandlungen. Zur Ausführungszeit sind eine List und eine List genau gleich; Die zusätzlichen Informationen wurden vom Compiler gelöscht .

Vergleichen Sie dies mit, sagen wir, C #, wo die Information zur Ausführungszeit beibehalten wird und Code erlaubt, Ausdrücke wie typeof(T) zu enthalten, was äquivalent zu T.class – außer dass letztere ungültig ist. (Es gibt weitere Unterschiede zwischen .NET-Generika und Java-Generika.) Typ-Löschung ist die Quelle vieler “seltsamer” Warnungen / Fehlermeldungen beim Umgang mit Java-Generika.

Andere Ressourcen:

  • Oracle-Dokumentation
  • Wikipedia
  • Gilad Brachas Java Generics Guide (PDF – sehr empfehlenswert; Link muss möglicherweise in regelmäßigen Abständen geändert werden)
  • Angelika Langers Java Generics FAQ

Nebenbei bemerkt, es ist eine interessante Übung, um zu sehen, was der Compiler tut, wenn er löscht – macht das ganze Konzept ein wenig leichter zu begreifen. Es gibt ein spezielles Flag, das Sie dem Compiler übergeben können, um Java-Dateien auszugeben, in denen die Generika gelöscht und umgewandelt wurden. Ein Beispiel:

 javac -XD-printflat -d output_dir SomeFile.java 

Die -printflat ist das Flag, das an den Compiler übergeben wird, der die Dateien generiert. (Der -XD Teil teilt javac mit, dass er es an die ausführbare jar-Datei weitergibt, die das Kompilieren statt javac -d output_dir , aber ich schweife ab …) Das -d output_dir ist notwendig, weil der Compiler einen Platz benötigt, um den neuen Platz zu finden. Java-Dateien.

Das tut natürlich mehr als nur zu löschen; alle automatischen Sachen, die der Compiler macht, werden hier gemacht. Zum Beispiel werden auch Standardkonstruktoren eingefügt, die neuen foreach-style for Schleifen werden zu regulären for Schleifen erweitert usw. Es ist schön, die kleinen Dinge zu sehen, die automatisch passieren.

Um die bereits sehr vollständige Jon Skeet-Antwort zu vervollständigen, müssen Sie feststellen, dass das Konzept des Typs Löschen aus der Notwendigkeit der Kompatibilität mit früheren Versionen von Java abgeleitet ist .

Auf der EclipseCon 2007 (nicht mehr verfügbar) vorgestellt, umfasste die Kompatibilität diese Punkte:

  • Quellkompatibilität (schön zu haben …)
  • Binärkompatibilität (Muss!)
  • Migrationskompatibilität
    • Bestehende Programme müssen weiter funktionieren
    • Vorhandene Bibliotheken müssen in der Lage sein, generische Typen zu verwenden
    • Haben müssen!

Ursprüngliche Antwort:

Daher:

 new ArrayList() => new ArrayList() 

Es gibt Vorschläge für eine größere Verdinglichung . Verfestigen Sie das Sein “Betrachten Sie ein abstraktes Konzept als real”, wobei Sprachkonstrukte Konzepte sein sollten, nicht nur syntaktischer Zucker.

Ich sollte auch die checkCollection Methode von Java 6 erwähnen, die eine dynamisch typsichere Ansicht der angegebenen Sammlung zurückgibt. Jeder Versuch, ein Element des falschen Typs einzufügen, führt zu einer sofortigen ClassCastException .

Der Generics-Mechanismus in der Sprache bietet eine Kompilierzeitprüfung (statisch), aber es ist möglich, diesen Mechanismus mit ungeprüften Umwandlungen zu umgehen .

In der Regel ist dies kein Problem, da der Compiler bei allen solchen nicht markierten Vorgängen Warnungen ausgibt.

Es gibt jedoch Zeiten, in denen die Überprüfung des statischen Typs allein nicht ausreicht, wie zum Beispiel:

  • Wenn eine Sammlung an eine Bibliothek eines Drittanbieters übergeben wird, ist es unbedingt erforderlich, dass der Bibliothekscode die Sammlung nicht durch Einfügen eines Elements des falschen Typs beschädigt.
  • Ein Programm schlägt mit einer ClassCastException fehl und ClassCastException , dass ein falsch typisiertes Element in eine parametrisierte Auflistung ClassCastException wurde. Leider kann die Ausnahme zu einem beliebigen Zeitpunkt auftreten, nachdem das errorshafte Element eingefügt wurde, so dass sie in der Regel nur wenige oder gar keine Informationen über die tatsächliche Ursache des Problems enthält.

Update Juli 2012, fast vier Jahre später:

Es ist jetzt (2012) detailliert in ” API Migration Compatibility Rules (Signature Test) ”

Die Java-Programmiersprache implementiert Generika unter Verwendung von Löschung, die sicherstellt, dass ältere und generische Versionen normalerweise identische classndateien erzeugen, mit Ausnahme einiger Zusatzinformationen über Typen. Die Binärkompatibilität ist nicht errorshaft, da es möglich ist, eine Legacy-classndatei durch eine generische classndatei zu ersetzen, ohne einen Clientcode zu ändern oder neu zu kompilieren.

Um die Verbindung mit nicht-generischem Legacy-Code zu erleichtern, ist es auch möglich, das Löschen eines parametrisierten Typs als Typ zu verwenden. Ein solcher Typ wird als Rohtyp bezeichnet ( Java Language Specification 3 / 4.8 ). Wenn Sie den Raw-Typ zulassen, ist auch die Rückwärtskompatibilität für den Quellcode gewährleistet.

Demnach sind die folgenden Versionen der class java.util.Iterator sowohl binär als auch abwärtskompatibel zu Quellcode:

 Class java.util.Iterator as it is defined in Java SE version 1.4: public interface Iterator { boolean hasNext(); Object next(); void remove(); } Class java.util.Iterator as it is defined in Java SE version 5.0: public interface Iterator { boolean hasNext(); E next(); void remove(); } 

Erasure bedeutet wörtlich, dass die Typinformation, die im Quellcode vorhanden ist, aus dem kompilierten Bytecode gelöscht wird. Lassen Sie uns das mit etwas Code verstehen.

 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenericsErasure { public static void main(String args[]) { List list = new ArrayList(); list.add("Hello"); Iterator iter = list.iterator(); while(iter.hasNext()) { String s = iter.next(); System.out.println(s); } } } 

Wenn Sie diesen Code kompilieren und dann mit einem Java-Decompiler dekompilieren, erhalten Sie so etwas. Beachten Sie, dass der dekompilierte Code keine Spur der Typinformationen enthält, die im ursprünglichen Quellcode vorhanden sind.

 import java.io.PrintStream; import java.util.*; public class GenericsErasure { public GenericsErasure() { } public static void main(String args[]) { List list = new ArrayList(); list.add("Hello"); String s; for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s)) s = (String)iter.next(); } } 

Ergänzt die bereits ergänzte Jon Skeet Antwort …

Es wurde erwähnt, dass die Implementierung von Generika durch Löschung zu einigen ärgerlichen Einschränkungen führt (z. B. kein new T[42] ). Es wurde auch erwähnt, dass der Hauptgrund dafür, die Dinge auf diese Weise zu tun, Rückwärtskompatibilität im Bytecode ist. Dies ist auch (meistens) wahr. Der Bytecode generiert-Ziel 1.5 ist etwas anders als gerade entzuckerten Casting-Ziel 1.4. Technisch gesehen ist es sogar (durch immense Tricks) möglich, während der Laufzeit auf generische Typ-Instanziierungen zuzugreifen, was beweist, dass tatsächlich etwas im Bytecode ist.

Der interessantere Punkt (der nicht angesprochen wurde) besteht darin, dass die Implementierung von Generika unter Verwendung von Löschung ein wenig mehr Flexibilität bietet, was das Hochstufensystem leisten kann. Ein gutes Beispiel dafür wäre Scalas JVM-Implementierung gegen CLR. Auf der JVM ist es möglich, höhere Arten direkt zu implementieren, da die JVM selbst keine generischen Typen einschränkt (da diese “Typen” effektiv nicht vorhanden sind). Dies steht im Gegensatz zur CLR, die Laufzeitwissen über Parameterinstanziierungen besitzt. Aus diesem Grund muss die CLR selbst ein Konzept dafür haben, wie Generika verwendet werden sollten, wodurch Versuche zunichte gemacht werden, das System mit unerwarteten Regeln zu erweitern. Als Ergebnis werden Scala’s höhere Arten auf der CLR implementiert, indem eine seltsame Form von Löschung verwendet wird, die innerhalb des Compilers selbst emuliert wird, was sie nicht vollständig kompatibel mit einfachen alten .NET-Generics macht.

Erasure kann unbequem sein, wenn Sie zur Laufzeit ungezogene Dinge tun wollen, aber es bietet den Compiler-Autoren die größte Flexibilität. Ich nehme an, das ist ein Teil davon, warum es nicht so bald weggehen wird.

Wie ich es verstehe (ein .NET- Typ), hat die JVM kein Konzept von Generika, also ersetzt der Compiler Typparameter durch Object und führt alle Castings für Sie aus.

Dies bedeutet, dass Java Generics nichts anderes als Syntaxzucker sind und keine performancesverbesserung für Werttypen bieten, die Boxen / Unboxing benötigen, wenn sie als Referenz übergeben werden.

Es gibt gute Erklärungen. Ich füge nur ein Beispiel hinzu, um zu zeigen, wie die Typlöschung mit einem Decompiler funktioniert.

Originalklasse,

 import java.util.ArrayList; import java.util.List; public class S { T obj; S(T o) { obj = o; } T getob() { return obj; } public static void main(String args[]) { List list = new ArrayList<>(); list.add("Hello"); // for-each for(String s : list) { String temp = s; System.out.println(temp); } // stream list.forEach(System.out::println); } } 

Dekompilierter Code aus seinem Bytecode,

 import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.Objects; import java.util.function.Consumer; public class S { Object obj; S(Object var1) { this.obj = var1; } Object getob() { return this.obj; } public static void main(String[] var0) { ArrayList var1 = new ArrayList(); var1.add("Hello"); // for-each Iterator iterator = var1.iterator(); while (iterator.hasNext()) { String string; String string2 = string = (String)iterator.next(); System.out.println(string2); } // stream PrintStream printStream = System.out; Objects.requireNonNull(printStream); var1.forEach(printStream::println); } } 

Generische Programmierung wird in Java 1.5 Version eingeführt
Vor allem was ist generisch in Java?
Generische Programmierung ist ein typsicheres Objekt. Vor der generischen Sammlung können wir jede Art von Objekt speichern. und nach generisch müssen wir die Daten eines bestimmten Objekttyps speichern.

Was sind die Vorteile von Generic?
Der Hauptvorteil von generic ist Typcasting ist nicht erforderlich und auch type-sage und Generic wird die Kompilierzeit überprüfen. und die allgemeine Syntax von Generic ist ClassOrInterface here type ist das Signal, dass diese class die class bei ihrer Instanziierung gesetzt haben kann

Beispiel . GenericClassDemo

genericclassDemo = new GenericClassDemo (Mitarbeiter.java)