Was ist ein Rohtyp und warum sollten wir ihn nicht verwenden?

481

Fragen:

  • Was sind Rohtypen in Java und warum höre ich oft, dass sie nicht in neuem Code verwendet werden sollten?
  • Was ist die Alternative, wenn wir Rohtypen nicht verwenden können, und wie ist es besser?
polygenelubricants 05.05.2010, 02:48
quelle

14 Antworten

583

Was ist ein Rohtyp?

Die Java-Sprachspezifikation definiert einen Rohtyp wie folgt:

JLS 4.8 Rohtypen

  

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 Rohtyps R , der nicht von einer Superklasse oder Superschnittstelle von R 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.

Was ist das Besondere an Rohtypen?

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.

%Vor%

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.

Siehe auch

Wie unterscheidet sich ein Rohtyp von der Verwendung von <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 Typ List<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 Sie List<String> an einen Parameter vom Typ List übergeben können, können Sie ihn nicht an einen Parameter vom Typ List<Object> übergeben. Es gibt Subtyping-Regeln für Generika und List<String> ist ein Subtyp des Rohtyps List , jedoch nicht des parametrisierten Typs List<Object> . Als Folge verlieren Sie die Typsicherheit, wenn Sie einen rohen Typ wie List verwenden, aber nicht, wenn Sie einen parametrisierten Typ wie List<Object> verwenden.

Um den Punkt zu veranschaulichen, betrachten Sie die folgende Methode, die ein List<Object> und ein new Object() anfügt.

%Vor%

Generika in Java sind invariant. A List<String> ist kein List<Object> , daher würde Folgendes eine Compiler-Warnung generieren:

%Vor%

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.

Siehe auch

Wie unterscheidet sich ein Rohtyp von der Verwendung von <?> 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.

Ein Rohtyp ist das Löschen dieses Typs

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 Rohtyps C , das nicht von seinen Oberklassen oder Superschnittstellen geerbt wird, ist der Rohtyp, der dem Löschen seines Typs im generischen Typ entspricht Deklaration entsprechend C .

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 !

zurückgibt

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 wie s und die Löschungen aller formalen Parametertypen in s .

     

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

Wenn es unsicher ist, warum darf ein roher Typ verwendet werden?

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 .

Gibt es keine Ausnahmen?

Da Java-Generika nicht verifiziert sind, gibt es leider zwei Ausnahmen, bei denen rohe Typen in neuem Code verwendet werden müssen:

  • Klassenliterale, z.B. List.class , nicht List<String>.class
  • instanceof Operand, z.B. o instanceof Set , nicht o instanceof Set<String>

Siehe auch

polygenelubricants 05.05.2010 04:50
quelle
46
  

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.

%Vor%

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.

%Vor%

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:

  • Jede in einem Object gespeicherte Collection muss umgewandelt werden, bevor sie verwendet werden kann
  • Die Verwendung von Generics ermöglicht die Überprüfung der Kompilierungszeit
  • Die Verwendung roher Typen ist dasselbe wie das Speichern von jedem Wert als 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

    
josefx 05.05.2010 21:50
quelle
20

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 :

%Vor%

Wenn das Argument des tatsächlichen Typs weggelassen wird, erstellen Sie einen unformatierten Typ von Box<T> :

%Vor%

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:

%Vor%

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.

Ungeprüfte Fehlermeldungen

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

    
Adelin 10.08.2013 23:58
quelle
16
%Vor%

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.

    
Bozho 16.06.2010 07:44
quelle
12

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.

    
Andy White 05.05.2010 02:58
quelle
10

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%     
Bert F 05.05.2010 04:31
quelle
10

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.

    
Michael Borgwardt 16.06.2010 07:53
quelle
8

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.

%Vor%

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

%Vor%     
Lars Andren 05.05.2010 04:44
quelle
8

Hier betrachte ich mehrere Fälle, durch die Sie das Konzept klären können

%Vor%

Fall 1

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.

%Vor%

Fall 2

In diesem Fall ist ArrayList<String> arr ein strikter Typ, aber Ihr Objekt new ArrayList(); ist ein unformatierter Typ.

%Vor%

hier arr ist ein Strict-Typ. Daher wird beim Hinzufügen von integer der Kompilierzeitfehler angezeigt.

  

Warnung : - Ein Raw Type Objekt wird auf eine Strict type referenzierte Variable von ArrayList bezogen.

Fall 3

In diesem Fall ist ArrayList arr ein Rohtyp, aber Ihr Objekt new ArrayList<String>(); ist ein Strict-Typ.

%Vor%

Es fügt einen beliebigen Objekttyp hinzu, weil arr ein Rohtyp ist.

  

Warnung : - Ein Strict Type Objekt wird auf eine raw type referenzierte Variable referenziert.

    
Vikrant Kashyap 13.05.2016 13:51
quelle
4

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%     
pakore 16.06.2010 07:47
quelle
4

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

    
GuyPaddock 04.12.2017 04:49
quelle
3

Tutorial-Seite .

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%     
Mykhaylo Adamovych 24.05.2016 13:42
quelle
1

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

    
user2442615 07.03.2016 02:24
quelle
1

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.

    
Stefan Reich 04.12.2017 12:58
quelle

Tags und Links