Ich habe auf der Oracle-Website über Javas Typlöschung gelesen .
Wann erfolgt die Typlöschung? Zur Kompilierungszeit oder zur Laufzeit? Wann wird die Klasse geladen? Wann wird die Klasse instanziiert?
Viele Websites (einschließlich des oben erwähnten offiziellen Tutorials) geben an, dass die Typlöschung zur Kompilierungszeit erfolgt. Wenn die Typinformationen beim Kompilieren vollständig entfernt werden, wie überprüft das JDK die Typkompatibilität, wenn eine Methode mit Generika ohne Typinformationen oder falsche Typinformationen aufgerufen wird?
Betrachten Sie das folgende Beispiel: Angenommen, die Klasse A
hat eine Methode empty(Box<? extends Number> b)
. Wir kompilieren A.java
und erhalten die Klassendatei A.class
.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Jetzt erstellen wir eine weitere Klasse, B
die die Methode empty
mit einem nicht parametrisierten Argument (Rohtyp) aufruft : empty(new Box())
. Wenn wir im Klassenpfad B.java
mit kompilieren A.class
, ist javac klug genug, um eine Warnung auszulösen. So A.class
hat irgendeine Art Informationen darin gespeichert.
public class B {
public static void invoke() {
// java: unchecked method invocation:
// method empty in class A is applied to given types
// required: Box<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
Meine Vermutung wäre, dass das Löschen des Typs auftritt, wenn die Klasse geladen wird, aber es ist nur eine Vermutung. Wann passiert es?
quelle
Antworten:
Die Typlöschung gilt für die Verwendung von Generika. Es gibt definitiv Metadaten in der Klassendatei, die angeben, ob eine Methode / ein Typ generisch ist oder nicht , und welche Einschränkungen es gibt usw. Wenn Generika verwendet werden , werden sie jedoch in Überprüfungen zur Kompilierungszeit und in Ausführungszeiten umgewandelt. Also dieser Code:
wird kompiliert in
Zur Ausführungszeit gibt es keine Möglichkeit herauszufinden, dass
T=String
für das Listenobjekt diese Informationen weg sind.... aber die
List<T>
Schnittstelle selbst wirbt immer noch als generisch.BEARBEITEN: Zur Verdeutlichung behält der Compiler die Informationen über die Variable a bei
List<String>
- aber das können SieT=String
für das Listenobjekt selbst immer noch nicht herausfinden .quelle
List<? extends InputStream>
wie können Sie dann wissen, welcher Typ es war, als er erstellt wurde? Auch wenn Sie herausfinden können , in welchem Feld die Referenz gespeichert wurde, warum sollten Sie dies tun müssen? Warum sollten Sie in der Lage sein, alle weiteren Informationen zum Objekt zur Ausführungszeit abzurufen, nicht jedoch die generischen Typargumente? Sie scheinen zu versuchen, das Löschen von Schriftarten zu einer winzigen Sache zu machen, die Entwickler nicht wirklich betrifft - obwohl ich es in einigen Fällen als sehr bedeutendes Problem empfinde.Der Compiler ist dafür verantwortlich, Generics zur Kompilierungszeit zu verstehen. Der Compiler ist auch dafür verantwortlich, dieses "Verständnis" generischer Klassen in einem Prozess, den wir als Typlöschung bezeichnen, wegzuwerfen . Alles geschieht zur Kompilierungszeit.
Hinweis: Entgegen der Meinung der meisten Java-Entwickler ist es möglich, Informationen vom Typ zur Kompilierungszeit beizubehalten und diese Informationen zur Laufzeit abzurufen, obwohl dies sehr eingeschränkt ist. Mit anderen Worten: Java bietet reified Generics auf sehr eingeschränkte Weise .
In Bezug auf die Art der Löschung
Beachten Sie, dass dem Compiler zur Kompilierungszeit vollständige Typinformationen zur Verfügung stehen. Diese Informationen werden jedoch im Allgemeinen absichtlich gelöscht, wenn der Bytecode generiert wird. Dies wird als Typlöschung bezeichnet . Dies geschieht aufgrund von Kompatibilitätsproblemen auf diese Weise: Die Absicht der Sprachentwickler bestand darin, vollständige Quellcodekompatibilität und vollständige Bytecodekompatibilität zwischen Versionen der Plattform bereitzustellen. Wenn es anders implementiert wäre, müssten Sie Ihre Legacy-Anwendungen neu kompilieren, wenn Sie auf neuere Versionen der Plattform migrieren. So wie es gemacht wurde, bleiben alle Methodensignaturen erhalten (Quellcode-Kompatibilität) und Sie müssen nichts neu kompilieren (Binärkompatibilität).
In Bezug auf reified Generics in Java
Wenn Sie Informationen zum Typ der Kompilierungszeit beibehalten müssen, müssen Sie anonyme Klassen verwenden. Der Punkt ist: Im ganz besonderen Fall anonymer Klassen ist es möglich, zur Laufzeit vollständige Informationen zum Typ der Kompilierungszeit abzurufen, was mit anderen Worten bedeutet: reifizierte Generika. Dies bedeutet, dass der Compiler keine Typinformationen wegwirft, wenn anonyme Klassen beteiligt sind. Diese Informationen werden im generierten Binärcode gespeichert, und das Laufzeitsystem ermöglicht es Ihnen, diese Informationen abzurufen.
Ich habe einen Artikel zu diesem Thema geschrieben:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
Ein Hinweis zu der im obigen Artikel beschriebenen Technik ist, dass die Technik für die Mehrheit der Entwickler unklar ist. Obwohl es funktioniert und gut funktioniert, fühlen sich die meisten Entwickler mit der Technik verwirrt oder unwohl. Wenn Sie eine gemeinsame Codebasis haben oder planen, Ihren Code für die Öffentlichkeit freizugeben, empfehle ich die oben beschriebene Technik nicht. Wenn Sie jedoch der einzige Benutzer Ihres Codes sind, können Sie die Leistung dieser Technik nutzen.
Beispielcode
Der obige Artikel enthält Links zum Beispielcode.
quelle
new Box<String>() {};
nicht bei indirektem Zugriff gespeichert werden,void foo(T) {...new Box<T>() {};...}
da der Compiler keine Typinformationen für speichert die beiliegende Methodendeklaration.Wenn Sie ein Feld haben, das ein generischer Typ ist, werden seine Typparameter in die Klasse kompiliert.
Wenn Sie eine Methode haben, die einen generischen Typ annimmt oder zurückgibt, werden diese Typparameter in die Klasse kompiliert.
Diese Informationen werden vom Compiler verwendet, um Ihnen mitzuteilen, dass Sie a nicht
Box<String>
an dieempty(Box<T extends Number>)
Methode übergeben können.Die API ist kompliziert, aber man kann diese Art Informationen über das Reflection - API mit Methoden wie inspiziert
getGenericParameterTypes
,getGenericReturnType
und für FeldergetGenericType
.Wenn Sie Code haben, der einen generischen Typ verwendet, fügt der Compiler nach Bedarf (im Aufrufer) Casts ein, um die Typen zu überprüfen. Die generischen Objekte selbst sind nur der Rohtyp; Der parametrisierte Typ wird "gelöscht". Wenn Sie also eine erstellen
new Box<Integer>()
, gibt es keine Informationen über dieInteger
Klasse imBox
Objekt.Angelika Langers FAQ ist die beste Referenz, die ich für Java Generics gesehen habe.
quelle
Generics in Java Language ist eine wirklich gute Anleitung zu diesem Thema.
Es ist also zur Kompilierungszeit. Die JVM wird nie wissen, welche
ArrayList
Sie verwendet haben.Ich würde auch die Antwort von Herrn Skeet zu Was ist das Konzept der Löschung in Generika in Java?
quelle
Das Löschen des Typs erfolgt zur Kompilierungszeit. Das Löschen von Typen bedeutet, dass der generische Typ und nicht jeder Typ vergessen wird. Außerdem wird es weiterhin Metadaten zu den generischen Typen geben. Beispielsweise
wird konvertiert zu
zur Kompilierungszeit. Möglicherweise erhalten Sie Warnungen nicht, weil der Compiler weiß, um welchen Typ es sich handelt, sondern im Gegenteil, weil er nicht genug weiß und daher keine Sicherheit für den Typ gewährleisten kann.
Darüber hinaus behält der Compiler die Typinformationen zu den Parametern eines Methodenaufrufs bei, die Sie über Reflection abrufen können.
Dieser Leitfaden ist der beste, den ich zu diesem Thema gefunden habe.
quelle
Der Begriff "Typlöschung" ist nicht wirklich die richtige Beschreibung des Problems von Java mit Generika. Das Löschen von Typen ist per se keine schlechte Sache, in der Tat ist es für die Leistung sehr notwendig und wird häufig in mehreren Sprachen wie C ++, Haskell, D verwendet.
Bevor Sie sich abschrecken, erinnern Sie sich bitte an die korrekte Definition der Typlöschung aus dem Wiki
Was ist Typlöschung?
Typlöschung bedeutet, dass zur Entwurfszeit erstellte Typ-Tags oder zur Kompilierungszeit abgeleitete Typ-Tags weggeworfen werden, sodass das kompilierte Programm im Binärcode keine Typ-Tags enthält. Dies ist für jede Programmiersprache der Fall, die zu Binärcode kompiliert wird, außer in einigen Fällen, in denen Sie Laufzeit-Tags benötigen. Diese Ausnahmen umfassen beispielsweise alle existenziellen Typen (subtypierbare Java-Referenztypen, Beliebiger Typ in vielen Sprachen, Union-Typen). Der Grund für das Löschen von Typen besteht darin, dass Programme in eine Sprache umgewandelt werden, die in einer Art uni-typisiert ist (Binärsprache erlaubt nur Bits), da Typen nur Abstraktionen sind und eine Struktur für ihre Werte und die entsprechende Semantik für ihre Verarbeitung festlegen.
Das ist also eine normale natürliche Sache.
Das Problem von Java ist anders und hängt davon ab, wie es sich ändert.
Die oft gemachten Aussagen über Java haben keine reifizierten Generika, sind auch falsch.
Java reifiziert zwar, aber aufgrund der Abwärtskompatibilität falsch.
Was ist Verdinglichung?
Aus unserem Wiki
Reifizierung bedeutet, durch Spezialisierung etwas Abstraktes (Parametrischer Typ) in etwas Konkretes (Konkreter Typ) umzuwandeln.
Wir veranschaulichen dies anhand eines einfachen Beispiels:
Eine ArrayList mit Definition:
ist eine Abstraktion, im Detail ein Typkonstruktor, der "spezialisiert" wird, wenn er auf einen konkreten Typ spezialisiert ist, sagen wir Integer:
Wo
ArrayList<Integer>
ist wirklich ein Typ.Aber genau das macht Java nicht !!! Stattdessen reifizieren sie ständig abstrakte Typen mit ihren Grenzen, dh sie produzieren denselben konkreten Typ unabhängig von den zur Spezialisierung übergebenen Parametern:
Dies wird hier mit dem implizit gebundenen Objekt (
ArrayList<T extends Object>
==ArrayList<T>
) bestätigt.Trotzdem macht es generische Arrays unbrauchbar und verursacht einige seltsame Fehler für Rohtypen:
es verursacht viele Unklarheiten als
beziehen sich auf die gleiche Funktion:
Daher kann das generische Überladen von Methoden in Java nicht verwendet werden.
quelle
Ich habe mit Typ Löschung in Android angetroffen. In der Produktion verwenden wir Gradle mit Minify-Option. Nach der Minimierung habe ich eine fatale Ausnahme. Ich habe eine einfache Funktion zum Anzeigen der Vererbungskette meines Objekts erstellt:
Und es gibt zwei Ergebnisse dieser Funktion:
Nicht minimierter Code:
Minimierter Code:
In minimiertem Code werden also tatsächlich parametrisierte Klassen durch Rohklassentypen ohne Typinformationen ersetzt. Als Lösung für mein Projekt habe ich alle Reflection-Aufrufe entfernt und durch explizite Parametertypen ersetzt, die in Funktionsargumenten übergeben wurden.
quelle