Die Java-Sprachspezifikation definiert einen Rohtyp wie folgt:
Ein Rohtyp ist definiert als einer von:
Der Referenztyp, der gebildet wird, indem der Name einer generischen Typdeklaration ohne zugehörige Argumentliste übernommen wird.
Ein Array-Typ, dessen Elementtyp ein unformatierter Typ ist.
Ein nicht
static
-Membertyp eines RohtypsR
, der nicht von einer Superklasse oder Superschnittstelle vonR
geerbt wurde.
Hier ein Beispiel zur Veranschaulichung:
%Vor% Hier ist MyType<E>
ein parametrisierter Typ ( JLS 4.5 ). Es ist üblich, diesen Typ umgangssprachlich einfach als MyType
zu bezeichnen, aber technisch ist der Name MyType<E>
.
mt
hat einen unformatierten Typ (und generiert eine Kompilierungswarnung) nach dem ersten Aufzählungspunkt in der obigen Definition; inn
hat auch einen unformatierten Typ nach dem dritten Aufzählungspunkt.
MyType.Nested
ist kein parametrisierter Typ, obwohl es ein Membertyp eines parametrisierten Typs MyType<E>
ist, weil es static
ist.
mt1
und mt2
werden beide mit tatsächlichen Typparametern deklariert, also sind sie keine unformatierten Typen.
Im Wesentlichen verhalten sich Rohtypen genau wie vor der Einführung von Generika. Das heißt, das Folgende ist zur Kompilierzeit vollständig zulässig.
%Vor%Der obige Code läuft gut, aber angenommen, Sie haben auch Folgendes:
%Vor% Jetzt treten Probleme zur Laufzeit auf, weil names
etwas enthält, das kein instanceof String
ist.
Vermutlich, wenn names
nur String
enthalten soll, könnten Sie vielleicht vielleicht immer noch einen unformatierten Typ verwenden und manuell alle add
selbst überprüfen und dann manuell auf String
jedes Element von names
anwenden. Noch besser , wenn Sie jedoch keinen Raw-Typ verwenden und den Compiler die ganze Arbeit für Sie erledigen lassen , nutzen Sie die Leistungsfähigkeit von Java-Generics.
Natürlich, wenn Sie DO möchten, dass names
ein Boolean
zulässt, dann können Sie es als List<Object> names
deklarieren, und der obige Code würde kompilieren.
<Object>
als Typparameter? Es folgt ein Zitat aus Effektive Java 2. Ausgabe, Punkt 23: Verwenden Sie keine rohen Typen in neuem Code :
Was ist der Unterschied zwischen dem Rohtyp
List
und dem parametrisierten TypList<Object>
? Locker gesagt, der erste hat die generische Typprüfung deaktiviert, während der letztere dem Compiler explizit gesagt hat, dass er Objekte jedes Typs halten kann. Während SieList<String>
an einen Parameter vom TypList
übergeben können, können Sie ihn nicht an einen Parameter vom TypList<Object>
übergeben. Es gibt Subtyping-Regeln für Generika undList<String>
ist ein Subtyp des RohtypsList
, jedoch nicht des parametrisierten TypsList<Object>
. Als Folge verlieren Sie die Typsicherheit, wenn Sie einen rohen Typ wieList
verwenden, aber nicht, wenn Sie einen parametrisierten Typ wieList<Object>
verwenden.
Um den Punkt zu veranschaulichen, betrachten Sie die folgende Methode, die ein List<Object>
und ein new Object()
anfügt.
Generika in Java sind invariant. A List<String>
ist kein List<Object>
, daher würde Folgendes eine Compiler-Warnung generieren:
Wenn Sie appendNewObject
so deklariert hätten, dass sie den rohen Typ List
als Parameter verwenden würden, würde dies kompilieren und Sie würden daher die Typensicherheit verlieren, die Sie von Generika erhalten.
<E extends Number>
? und <Number>
? <?>
als Typparameter? List<Object>
, List<String>
usw. sind alle List<?>
, daher mag es verlockend sein zu sagen, dass sie nur List
sind. Es gibt jedoch einen großen Unterschied: Da ein List<E>
nur add(E)
definiert, können Sie einem List<?>
nicht irgendein beliebiges Objekt hinzufügen. Auf der anderen Seite, da der rohe Typ List
keine Typsicherheit hat, können Sie add
fast alles zu einem List
machen.
Betrachten Sie die folgende Variante des vorherigen Snippets:
%Vor% Der Compiler hat Sie wunderbar davor bewahrt, die Typinvarianz von List<?>
! zu verletzen! Wenn Sie den Parameter als Raw-Typ List list
deklariert hätten, würde der Code kompiliert und Sie würden die Typinvariante von List<String> names
verletzen.
Zurück zu JLS 4.8:
Es ist möglich, als Typ das Löschen eines parametrierten Typs oder das Löschen eines Array-Typs zu verwenden, dessen Elementtyp ein parametrisierter Typ ist. Solch ein Typ wird als roher Typ bezeichnet.
[...]
Die Superklassen (bzw. Superinterfaces) eines Rohtyps sind die Löschungen der Superklassen (Superinterfaces) einer beliebigen Parametrisierung des generischen Typs.
Der Typ eines Konstruktors, einer Instanzmethode oder eines nicht
static
-Feldes eines RohtypsC
, das nicht von seinen Oberklassen oder Superschnittstellen geerbt wird, ist der Rohtyp, der dem Löschen seines Typs im generischen Typ entspricht Deklaration entsprechendC
.
Einfacher ausgedrückt: Wenn ein Raw-Typ verwendet wird, werden die Konstruktoren, Instanzmethoden und nicht static
-Felder auch gelöscht () .
Nehmen Sie das folgende Beispiel:
%Vor% Wenn wir die rohe MyType
verwenden, wird auch getNames
gelöscht, so dass sie eine rohe List
!
JLS 4.6 erläutert weiterhin Folgendes :
Typ löscht auch die Signatur eines Konstruktors oder einer Methode auf eine Signatur, die keine parametrisierten Typen oder Typvariablen hat. Das Löschen einer Konstruktor- oder Methodensignatur
s
ist eine Signatur, bestehend aus Derselbe Name wies
und die Löschungen aller formalen Parametertypen ins
.Der Rückgabetyp einer Methode und die Typparameter einer generischen Methode oder eines generischen Konstruktors werden ebenfalls gelöscht, wenn die Signatur der Methode oder des Konstruktors gelöscht wird.
Das Löschen der Signatur einer generischen Methode hat keine Typparameter.
Der folgende Fehlerbericht enthält einige Gedanken von Maurizio Cimadamore, einem Compiler-Entwickler, und Alex Buckley, einem der Autoren der JLS, darüber, warum diese Art von Verhalten auftreten sollte: Ссылка . (Kurz gesagt, es macht die Spezifikation einfacher.)
Hier ist ein weiteres Zitat von JLS 4.8:
Die Verwendung von Rohtypen ist nur als Zugeständnis an die Kompatibilität von Legacy-Code zulässig. Von der Verwendung von Raw-Typen in Code, der nach der Einführung von Generizität in die Java-Programmiersprache geschrieben wurde, wird dringend abgeraten. Es ist möglich, dass zukünftige Versionen der Java-Programmiersprache die Verwendung von unformatierten Typen verbieten.
Effektive Java 2nd Edition muss auch hinzufügen:
Da Sie keine Rohtypen verwenden sollten, warum haben die Sprachdesigner das erlaubt? Um Kompatibilität zu gewährleisten.
Die Java-Plattform war gerade dabei, in ihr zweites Jahrzehnt zu gehen, als Generika eingeführt wurden, und es gab eine enorme Menge Java-Code, der keine Generika verwendete. Es wurde als kritisch erachtet, dass all dieser Code legal und interoperabel mit neuem Code bleibt, der Generika verwendet. Es musste legal sein, um Instanzen von parametrisierten Typen an Methoden zu übergeben, die für die Verwendung mit gewöhnlichen Typen ausgelegt waren, und umgekehrt. Diese Anforderung, die als Migrationskompatibilität bekannt ist, führte zu der Entscheidung, unformatierte Typen zu unterstützen.
Zusammenfassend sollten rohe Typen NIEMALS in neuem Code verwendet werden. Sie sollten immer parametrisierte Typen verwenden .
Da Java-Generika nicht verifiziert sind, gibt es leider zwei Ausnahmen, bei denen rohe Typen in neuem Code verwendet werden müssen:
List.class
, nicht List<String>.class
instanceof
Operand, z.B. o instanceof Set
, nicht o instanceof Set<String>
Collection<String>.class
Illegal? Was sind Rohtypen in Java, und warum höre ich oft, dass sie nicht in neuem Code verwendet werden sollten?
Rohtypen sind eine alte Geschichte der Java-Sprache. Am Anfang gab es Collections
und sie hielten Objects
nicht mehr und nicht weniger. Jede Operation in Collections
benötigt Umwandlungen von Object
in den gewünschten Typ.
Während dies die meiste Zeit funktionierte, traten Fehler auf.
%Vor% Die alten typlosen Sammlungen konnten die Typensicherheit nicht erzwingen, daher musste sich der Programmierer erinnern, was er in einer Sammlung gespeichert hatte.
Generics wurden erfunden, um diese Einschränkung zu umgehen, der Entwickler würde den gespeicherten Typ einmal deklarieren und der Compiler würde es stattdessen tun.
Zum Vergleich:
%Vor%Komplexer die Compareable Schnittstelle:
%Vor% Beachten Sie, dass es unmöglich ist, die Schnittstelle CompareAble
mit compareTo(MyCompareAble)
mit Rohtypen zu implementieren.
Warum sollten Sie sie nicht benutzen:
Object
gespeicherte Collection
muss umgewandelt werden, bevor sie verwendet werden kann Object
Was der Compiler macht: Generics sind abwärtskompatibel, sie verwenden die gleichen Java-Klassen wie die rohen Typen. Die Magie passiert meistens zur Kompilierzeit.
%Vor%Wird wie folgt übersetzt:
%Vor% Dies ist der gleiche Code, den Sie schreiben würden, wenn Sie die Rohtypen direkt verwenden würden. Dachte ich bin mir nicht sicher, was mit der CompareAble
-Schnittstelle passiert, schätze ich, dass es zwei compareTo
-Funktionen erzeugt, eine davon ein MyCompareAble
und die andere ein Object
und es nach dem Casting an die erste übergeben.
Was sind die Alternativen zu Rohtypen: Verwenden Sie Generika
Ein raw-Typ ist der Name einer generischen Klasse oder Schnittstelle ohne Argumente. Zum Beispiel angesichts der generischen Box-Klasse:
%Vor% Um einen parametrisierten Typ von Box<T>
zu erstellen, liefern Sie ein Argument vom Typ "real" für den Formal-Typ-Parameter T
:
Wenn das Argument des tatsächlichen Typs weggelassen wird, erstellen Sie einen unformatierten Typ von Box<T>
:
Daher ist Box
der Rohtyp des generischen Typs Box<T>
. Ein nicht generischer Klassen- oder Schnittstellentyp ist jedoch kein unformatierter Typ.
Rohtypen werden im Legacy-Code angezeigt, da viele API-Klassen (wie die Collections-Klassen) vor JDK 5.0 nicht generisch waren. Wenn Sie rohe Typen verwenden, erhalten Sie im Wesentlichen Vorgenerik-Verhalten - ein Box
gibt Ihnen Object
s. Aus Gründen der Abwärtskompatibilität ist das Zuweisen eines parametrisierten Typs zu seinem Rohtyp zulässig:
Aber wenn Sie einem parametrisierten Typ einen Rohtyp zuweisen, erhalten Sie eine Warnung:
%Vor%Sie erhalten auch eine Warnung, wenn Sie einen Raw-Typ verwenden, um generische Methoden aufzurufen, die im entsprechenden generischen Typ definiert sind:
%Vor%Die Warnung zeigt, dass unformatierte Typen generische Typprüfungen umgehen und das Auffangen von unsicherem Code zur Laufzeit zurückstellen. Daher sollten Sie die Verwendung von Rohtypen vermeiden.
Der Abschnitt Type Erasure enthält weitere Informationen darüber, wie der Java-Compiler Rohtypen verwendet.
Wie bereits erwähnt, können Sie beim Mischen von Legacy-Code mit generischem Code Warnmeldungen sehen, die der folgenden ähneln:
Hinweis: Beispiel.java verwendet nicht markierte oder unsichere Operationen.
Hinweis: Kompilieren Sie mit -Xlint: deaktiviert für Details.
Dies kann passieren, wenn Sie eine ältere API verwenden, die mit unformatierten Typen arbeitet, wie im folgenden Beispiel gezeigt:
%Vor%Der Begriff "ungeprüft" bedeutet, dass der Compiler nicht genügend Typinformationen hat, um alle Typprüfungen durchzuführen, die notwendig sind, um die Typensicherheit zu gewährleisten. Die Warnung "unchecked" ist standardmäßig deaktiviert, obwohl der Compiler einen Hinweis gibt. Um alle Warnungen anzuzeigen, die nicht aktiviert sind, kompilieren Sie sie mit -Xlint: unchecked.
Wenn Sie das vorherige Beispiel mit -Xlint neu zusammensetzen: deaktiviert, werden folgende zusätzliche Informationen angezeigt:
%Vor% Um nicht markierte Warnungen vollständig zu deaktivieren, verwenden Sie das Flag -Xlint: -unchecked. Die @SuppressWarnings("unchecked")
Annotation unterdrückt unkontrollierte Warnungen. Wenn Sie mit der @SuppressWarnings
-Syntax nicht vertraut sind, siehe Anmerkungen.
Ursprüngliche Quelle: Java-Lernprogramme
Sie sollten den Typparameter angeben.
Die Warnung weist darauf hin, dass Typen definiert sind, die Generika unterstützen sollte parametrisiert werden, anstatt ihre rohe Form zu verwenden.
List
ist zur Unterstützung von Generics definiert: public class List<E>
. Dies ermöglicht viele typsichere Operationen, die zur Kompilierzeit überprüft werden.
Ein "roher" Typ in Java ist eine Klasse, die nicht generisch ist und sich mit "rohen" Objekten anstatt mit typsicheren generischen Typparametern beschäftigt.
Bevor beispielsweise Java-Generics verfügbar waren, würden Sie eine Collection-Klasse wie diese verwenden:
%Vor%Wenn Sie Ihr Objekt zur Liste hinzufügen, ist es egal, um welchen Objekttyp es sich handelt, und wenn Sie es aus der Liste abrufen, müssen Sie es explizit in den erwarteten Typ umwandeln.
Mit Generics entfernen Sie den Faktor "unbekannt", weil Sie explizit angeben müssen, welche Art von Objekten in die Liste aufgenommen werden kann:
%Vor%Beachten Sie, dass bei Generika das Objekt, das vom get-Aufruf kommt, nicht umgewandelt werden muss. Die Sammlung ist daher so vordefiniert, dass sie nur mit MyObject funktioniert. Gerade diese Tatsache ist der Hauptantriebsfaktor für Generika. Es ändert eine Quelle von Laufzeitfehlern in etwas, das zur Kompilierzeit überprüft werden kann.
Was ist ein roher Typ und warum höre ich oft, dass sie nicht in neuem Code verwendet werden sollten?
Ein "roher Typ" ist die Verwendung einer generischen Klasse, ohne ein Typargument für seine parametrisierten Typen, z. Verwenden von List
anstelle von List<String>
. Wenn Generika in Java eingeführt wurden, wurden mehrere Klassen aktualisiert, um Generika zu verwenden. Die Verwendung dieser Klasse als "Raw-Typ" (ohne Angabe eines Typarguments) erlaubte die Kompilierung von Legacy-Code.
"Raw types" werden zur Abwärtskompatibilität verwendet. Ihre Verwendung in neuem Code wird nicht empfohlen, da die Verwendung der generischen Klasse mit einem type-Argument eine stärkere Typisierung ermöglicht, was wiederum die Code-Verständlichkeit verbessern und dazu führen kann, dass potenzielle Probleme früher erkannt werden.
Was ist die Alternative, wenn wir Rohtypen nicht verwenden können, und wie ist es besser?
Die bevorzugte Alternative besteht darin, generische Klassen wie vorgesehen zu verwenden - mit einem geeigneten Argument vom Typ (z. B. List<String>
). Dies ermöglicht es dem Programmierer, Typen spezifischer zu spezifizieren, den zukünftigen Betreuern mehr Bedeutung über die beabsichtigte Verwendung einer Variablen oder Datenstruktur zu vermitteln und es dem Compiler zu ermöglichen, eine bessere Typ-Sicherheit durchzusetzen. Diese Vorteile zusammen können die Codequalität verbessern und die Einführung einiger Codierungsfehler verhindern.
Zum Beispiel für eine Methode, bei der der Programmierer sicherstellen will, dass eine Listenvariable namens 'Namen' nur Strings enthält:
%Vor%Der Compiler möchte, dass Sie Folgendes schreiben:
%Vor%, sonst könnten Sie einen beliebigen Typ in list
hinzufügen, wodurch die Instanziierung als new ArrayList<String>()
sinnlos wird. Java-Generics sind nur ein Kompilierzeit-Feature, so dass ein mit new ArrayList<String>()
erstelltes Objekt gerne Integer
oder JFrame
-Elemente akzeptiert, wenn es einem Verweis vom "rohen Typ" List
zugeordnet ist - über das das Objekt selbst nichts weiß welche Typen es enthalten soll, tut nur der Compiler.
Ein roher -Typ ist das Fehlen eines -Typ-Parameters bei Verwendung eines generischen Typs.
Raw-type sollte nicht verwendet werden, da dies zu Laufzeitfehlern führen kann, z. B. das Einfügen eines double
in das, was ein Set
von int
s sein sollte.
Beim Abrufen des Materials aus Set
wissen Sie nicht, was herauskommt. Angenommen, Sie erwarten, dass es sich um int
s handelt, und Sie es in Integer
umwandeln; Ausnahme zur Laufzeit, wenn die double
3.45 mitkommt.
Wenn Sie einen -Typ-Parameter zu Ihrem Set
hinzufügen, erhalten Sie sofort einen Kompilierfehler. Dieser Präventivfehler ermöglicht es Ihnen, das Problem zu beheben, bevor während der Laufzeit etwas explodiert (das spart Zeit und Aufwand).
Hier betrachte ich mehrere Fälle, durch die Sie das Konzept klären können
%Vor% ArrayList<String> arr
Es ist eine ArrayList
Referenzvariable mit dem Typ String
, die auf ein ArralyList
Objekt des Typs String
verweist. Es bedeutet, dass es nur String-Objekt speichern kann.
Es ist ein Strict to String
und kein Raw Type, daher wird niemals eine Warnung ausgegeben.
In diesem Fall ist ArrayList<String> arr
ein strikter Typ, aber Ihr Objekt new ArrayList();
ist ein unformatierter Typ.
hier arr
ist ein Strict-Typ. Daher wird beim Hinzufügen von integer
der Kompilierzeitfehler angezeigt.
Warnung : - Ein
Raw
Type Objekt wird auf eineStrict
type referenzierte Variable vonArrayList
bezogen.
In diesem Fall ist ArrayList arr
ein Rohtyp, aber Ihr Objekt new ArrayList<String>();
ist ein Strict-Typ.
Es fügt einen beliebigen Objekttyp hinzu, weil arr
ein Rohtyp ist.
Warnung : - Ein
Strict
Type Objekt wird auf eineraw
type referenzierte Variable referenziert.
Sie sagen, dass Ihr list
ein List
von nicht spezifizierten Objekten ist. Das bedeutet, dass Java nicht weiß, welche Art von Objekten sich in der Liste befinden. Wenn Sie die Liste dann iterieren wollen, müssen Sie jedes Element umwandeln, um auf die Eigenschaften dieses Elements (in diesem Fall String) zugreifen zu können.
Im Allgemeinen ist es besser, die Sammlungen zu parametrisieren, so dass Sie keine Konvertierungsprobleme haben, Sie können nur Elemente des parametrisierten Typs hinzufügen und Ihr Editor wird Ihnen die geeigneten Methoden zur Auswahl anbieten.
> %Vor%Hier ist ein weiterer Fall, bei dem rohe Typen Sie beißen werden:
%Vor% Wie in der angenommenen Antwort erwähnt wurde, verlieren Sie alle Unterstützung für Generika innerhalb des Codes des Rohtyps. Jeder Typparameter wird in seine Löschung konvertiert (was im obigen Beispiel nur Object
ist).
Ein raw-Typ ist der Name einer generischen Klasse oder Schnittstelle ohne Argumente. Zum Beispiel angesichts der generischen Box-Klasse:
%Vor%Um einen parametrisierten Typ von Box zu erstellen, geben Sie ein tatsächliches Typargument für den formalen Typparameter T:
an %Vor%Wenn das Argument des tatsächlichen Typs weggelassen wird, erstellen Sie einen unformatierten Typ von Box:
%Vor%Ich habe diese Seite gefunden, nachdem ich einige Beispielübungen gemacht habe und genau die gleiche Verwirrung hatte.
============== Ich ging von diesem Code aus, wie durch das Beispiel ======================================== %Vor%
====================== Dieser Code ================================ ==
%Vor%================================================ ===============================
Es ist vielleicht sicherer, aber es hat 4 Stunden gedauert, um die Philosophie zu überwinden ...
Rohtypen sind in Ordnung, wenn sie ausdrücken, was Sie ausdrücken möchten.
Eine Deserialisierungsfunktion gibt beispielsweise List
zurück, kennt jedoch den Elementtyp der Liste nicht. Also List
ist hier der geeignete Rückgabetyp.