Fragen:
- Was sind Raw-Typen in Java und warum höre ich oft, dass sie nicht in neuem Code verwendet werden sollen?
- Was ist die Alternative, wenn wir keine Raw-Typen verwenden können, und warum ist sie besser?
Die Java Sprachspezifikation definiert eine Rohtyp wie folgt:
Ein Rohtyp ist definiert als einer der folgenden Typen:
Der Referenztyp, der aus dem Namen einer generischen Typdeklaration ohne zugehörige Typargumentliste gebildet wird.
Ein Array-Typ, dessen Elementtyp ein Rohtyp ist.
A nicht
static
Member-Typ eines Raw-TypsR
die nicht von einer Oberklasse oder einem Superinterface vonR
.
Hier ein Beispiel zur Veranschaulichung:
public class MyType<E> {
class Inner { }
static class Nested { }
public static void main(String[] args) {
MyType mt; // warning: MyType is a raw type
MyType.Inner inn; // warning: MyType.Inner is a raw type
MyType.Nested nest; // no warning: not parameterized type
MyType<Object> mt1; // no warning: type parameter given
MyType<?> mt2; // no warning: type parameter given (wildcard OK!)
}
}
Hier, MyType<E>
ist eine parametrierter Typ ( JLS 4.5 ). Umgangssprachlich bezeichnet man diesen Typ einfach als MyType
abgekürzt, aber eigentlich lautet der Name MyType<E>
.
mt
hat einen rohen Typ (und erzeugt eine Kompilierungswarnung) durch den ersten Aufzählungspunkt in der obigen Definition; inn
hat im dritten Aufzählungspunkt auch einen Rohtyp.
MyType.Nested
kein parametrisierter Typ ist, obwohl er ein Membertyp eines parametrisierten Typs ist MyType<E>
denn es ist static
.
mt1
y mt2
werden beide mit aktuellen Typparametern deklariert, sind also keine Rohtypen.
Im Wesentlichen verhalten sich raw types genauso wie vor der Einführung der Generika. Das heißt, das Folgende ist zur Kompilierzeit völlig legal.
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!
Der obige Code funktioniert einwandfrei, aber nehmen Sie an, Sie haben auch den folgenden Code:
for (Object o : names) {
String name = (String) o;
System.out.println(name);
} // throws ClassCastException!
// java.lang.Boolean cannot be cast to java.lang.String
Jetzt gibt es zur Laufzeit Probleme, denn names
etwas enthält, das kein instanceof String
.
Vermutlich, wenn Sie wollen names
nur zu enthalten String
Sie könnte vielleicht noch einen Rohtyp verwenden und manuell jede add
selbst und dann manuell gegossen à String
jedes Element aus names
. Noch besser ist jedoch NICHT die Verwendung eines rohen Typs und lassen Sie den Compiler die ganze Arbeit für Sie machen , die die Leistungsfähigkeit der Java-Generik nutzen.
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
Natürlich, wenn Sie DO wollen names
um eine Boolean
dann können Sie es deklarieren als List<Object> names
und der obige Code würde kompiliert.
<Object>
als Typ-Parameter?Im Folgenden ein Zitat aus Effective Java 2nd Edition, Punkt 23: Verwenden Sie keine rohen Typen in neuem Code :
Was genau ist der Unterschied zwischen dem Rohtyp
List
und der parametrisierte TypList<Object>
? Im ersten Fall wurde die generische Typprüfung abgeschafft, während im zweiten Fall dem Compiler ausdrücklich mitgeteilt wurde, dass er Objekte beliebigen Typs aufnehmen kann. Während Sie eineList<String>
auf einen Parameter des TypsList
können Sie ihn nicht an einen Parameter des TypsList<Object>
. Es gibt Subtypisierungsregeln für Generika, undList<String>
ist ein Subtyp des RohtypsList
aber nicht von dem parametrisierten TypList<Object>
. Daraus folgt, verlieren Sie die Typsicherheit, wenn Sie rohe Typen wieList
verwenden, aber nicht, wenn Sie einen parametrisierten Typ wieList<Object>
.
Zur Veranschaulichung soll die folgende Methode dienen, bei der eine List<Object>
und fügt eine new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Generika in Java sind invariant. A List<String>
ist keine List<Object>
so dass das folgende eine Compiler-Warnung erzeugen würde:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
Wenn Sie erklärt hätten appendNewObject
um einen Rohtyp zu nehmen List
als Parameter, dann würde dies kompilieren, und Sie würden daher verlieren die Typsicherheit, die Sie von Generika erhalten.
<?>
als Typ-Parameter?List<Object>
, List<String>
usw. sind alle List<?>
Es mag also verlockend sein, zu sagen, dass sie einfach nur List
stattdessen. Es gibt jedoch einen wesentlichen Unterschied: Da ein List<E>
definiert nur add(E)
können Sie nicht einfach irgendein beliebiges Objekt zu einer List<?>
. Andererseits, da der Rohtyp List
nicht über Typsicherheit verfügt, können Sie add
so ziemlich alles zu einem List
.
Betrachten Sie die folgende Variante des vorherigen Ausschnitts:
static void appendNewObject(List<?> list) {
list.add(new Object()); // compilation error!
}
//...
List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!
Der Compiler hat Sie auf wunderbare Weise davor geschützt, die Typinvarianz der List<?>
! Wenn Sie den Parameter als Rohtyp deklariert hätten List list
zu kompilieren, würde der Code kompiliert werden, und Sie würden die Typinvariante von List<String> names
.
Zurück zu JLS 4.8:
Es ist möglich, als Typ zu verwenden die Auslöschung eines parametrisierten Typs oder die Löschung eines Array-Typs, dessen Elementtyp ein parametrisierter Typ ist. Ein solcher Typ wird als Rohtyp .
[...]
Die Superklassen (bzw. Superschnittstellen) eines Rohtyps sind die Löschungen der Superklassen (Superschnittstellen) aller Parametrisierungen des generischen Typs.
Der Typ eines Konstruktors, einer Instanzmethode oder eines nicht
static
Feld eines RohtypsC
der nicht von seinen Oberklassen oder Superschnittstellen geerbt wird, ist der Rohtyp, der der Löschung seines Typs in der generischen Deklaration entspricht, die derC
.
Einfacher ausgedrückt: Wenn ein Rohtyp verwendet wird, werden die Konstruktoren, Instanzmethoden und nicht static
Felder sind auch gelöscht .
Nehmen Sie das folgende Beispiel:
class MyType<E> {
List<String> getNames() {
return Arrays.asList("John", "Mary");
}
public static void main(String[] args) {
MyType rawType = new MyType();
// unchecked warning!
// required: List<String> found: List
List<String> names = rawType.getNames();
// compilation error!
// incompatible types: Object cannot be converted to String
for (String str : rawType.getNames())
System.out.print(str);
}
}
Wenn wir die Rohdaten verwenden MyType
, getNames
wird ebenfalls gelöscht, so dass es einen rohen List
!
JLS 4.6 erklärt weiterhin Folgendes:
Die Typlöschung bildet auch die Signatur eines Konstruktors oder einer Methode auf eine Signatur ab, die keine parametrisierten Typen oder Typvariablen enthält. Die Löschung eines Konstruktors oder einer Methodensignatur
s
ist eine Signatur, die aus demselben Namen wies
und die Auslöschungen aller formalen Parametertypen, die ins
.Der Rückgabetyp einer Methode und die Typparameter einer generischen Methode oder eines Konstruktors werden ebenfalls gelöscht, wenn die Signatur der Methode oder des Konstruktors gelöscht wird.
Die Löschung der Signatur einer generischen Methode hat keine Typparameter.
Der folgende Fehlerbericht enthält einige Überlegungen von Maurizio Cimadamore, einem Compiler-Entwickler, und Alex Buckley, einem der Autoren des JLS, warum diese Art von Verhalten auftreten sollte: https://bugs.openjdk.java.net/browse/JDK-6400189 . (Kurz gesagt, es macht die Spezifikation einfacher.)
Hier ein weiteres Zitat aus JLS 4.8:
Die Verwendung von Rohtypen ist nur als Zugeständnis an die Kompatibilität von Legacy-Code erlaubt. Von der Verwendung von Rohtypen in Code, der nach der Einführung der Generizität in der Programmiersprache Java geschrieben wurde, wird dringend abgeraten. Es ist möglich, dass zukünftige Versionen der Java-Programmiersprache die Verwendung von Rohtypen verbieten werden.
Effektives Java 2. Auflage hat auch dies hinzuzufügen:
Wenn man davon ausgeht, dass man keine Rohtypen verwenden sollte, warum haben die Sprachentwickler sie dann zugelassen? Um Kompatibilität zu gewährleisten.
Die Java-Plattform war kurz davor, in ihr zweites Jahrzehnt einzutreten, als die Generics eingeführt wurden, und es gab eine enorme Menge an Java-Code, der keine Generics verwendete. Es wurde als entscheidend erachtet, dass all dieser Code legal bleibt und mit neuem Code, der Generics verwendet, interoperabel ist. Es musste möglich sein, Instanzen von parametrisierten Typen an Methoden zu übergeben, die für die Verwendung mit gewöhnlichen Typen vorgesehen waren, und umgekehrt. Diese Anforderung, bekannt als Migrationskompatibilität war ausschlaggebend für die Entscheidung, Rohtypen zu unterstützen.
Zusammenfassend lässt sich sagen, dass rohe Typen NIEMALS in neuem Code verwendet werden sollten. Sie sollten immer parametrisierte Typen verwenden .
Da Java-Generics nicht reifiziert 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>
Für die zweite Ausnahme ist die Syntax o instanceof Set
ebenfalls zulässig, um den Roh typ zu vermeiden (obwohl es in diesem Fall nur oberflächlich ist).
Was sind Rohdatentypen in Java und warum höre ich oft, dass sie nicht in neuem Code verwendet werden sollten?
Rohdatentypen sind Geschichte der Java-Sprache. Am Anfang gab es Collections
und sie hielten nur Objects
- nicht mehr und nicht weniger. Jede Operation auf Collections
erforderte Umwandlungen von Object
in den gewünschten Typ.
List aList = new ArrayList();
String s = "Hallo Welt!";
aList.add(s);
String c = (String)aList.get(0);
Obwohl dies meistens funktionierte, traten manchmal Fehler auf.
List aNumberList = new ArrayList();
String one = "1";//Zahl eins
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Führt zu ClassCastException
Die alten typlosen Sammlungen konnten die Typensicherheit nicht erzwingen, so dass der Programmierer sich daran erinnern musste, was in einer Sammlung gespeichert wurde.
Generics wurden erfunden, um diese Beschränkung zu umgehen. Der Entwickler würde den gespeicherten Typ einmal deklarieren und der Compiler würde dies dann tun.
List aNumberList = new ArrayList();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Kompilierungsfehler
String sOne = aNumberList.get(0);//funktioniert einwandfrei
Zum Vergleich:
// Alte Stil-Sammlungen mittlerweile als Rohdatentypen bekannt
List aList = new ArrayList(); //Könnte alles enthalten
// Neuer Stil-Sammlungen mit Generics
List aList = new ArrayList(); //Enthält nur Strings
Etwas Komplexeres die Compareable-Schnittstelle:
//Raw, nicht typensicher kann mit anderen Klassen vergleichen
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(Object other)
{return this.id - ((MyCompareAble)other).id;}
}
//Generisch
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(MyCompareAble other)
{return this.id - other.id;}
}
Beachten Sie, dass es unmöglich ist, die Schnittstelle CompareAble
mit compareTo(MyCompareAble)
mit Rohdatentypen zu implementieren. Warum man sie nicht verwenden sollte:
Object
, das in einer Collection
gespeichert wird, muss vor der Verwendung umgewandelt werdenObject
Was der Compiler macht: Generics sind abwärtskompatibel, sie verwenden die gleichen Java-Klassen wie die Rohdatentypen. Das meiste passiert zur Kompilierungszeit.
List someStrings = new ArrayList();
someStrings.add("one");
String one = someStrings.get(0);
Wird kompiliert als:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
Dies ist derselbe Code, den Sie schreiben würden, wenn Sie die Rohdatentypen direkt verwenden würden. Ich bin mir jedoch nicht sicher, was mit der CompareAble
-Schnittstelle passiert, ich nehme an, dass zwei compareTo
-Funktionen erstellt werden, eine nimmt einen MyCompareAble
entgegen und die andere nimmt ein Object
entgegen und übergibt es nach einer Umwandlung an die erste.
Was sind die Alternativen zu Rohdatentypen: Verwenden Sie Generics
Ein Rohdatentyp ist der Name einer generischen Klasse oder eines generischen Interfaces ohne irgendwelche Typargumente. Zum Beispiel, gegeben die generische Klasse Box:
public class Box {
public void set(T t) { /* ... */ }
// ...
}
Um einen parametrisierten Typ von Box
zu erstellen, geben Sie einen tatsächlichen Typargument für den formalen Typenparameter T
an:
Box intBox = new Box<>();
Wenn das tatsächliche Typargument ausgelassen wird, erstellen Sie einen Rohdatentyp von Box
:
Box rawBox = new Box();
Deshalb ist Box
der Rohdatentyp des generischen Typs Box
. Allerdings ist ein nicht-generischer Klassen- oder Interface-Typ kein Rohdatentyp.
Rohdatentypen erscheinen in Legacy-Code, weil viele API-Klassen (wie die Collections-Klassen) vor JDK 5.0 nicht generisch waren. Wenn Sie Rohdatentypen verwenden, erhalten Sie im Wesentlichen das Verhalten vor Generics - eine Box
gibt Ihnen Object
s. Für Abwärtskompatibilität ist es erlaubt, einem parametrisierten Typen seinen Rohdatentyp zuzuweisen:
Box stringBox = new Box<>();
Box rawBox = stringBox; // OK
Aber wenn Sie einem Rohdatentypen einen parametrisierten Typen zuweisen, erhalten Sie eine Warnung:
Box rawBox = new Box(); // rawBox ist ein Rohdatentyp von Box
Box intBox = rawBox; // Warnung: unbeaufsichtigte Umwandlung
Sie erhalten auch eine Warnung, wenn Sie einen Rohdatentypen verwenden, um generische Methoden aufzurufen, die im entsprechenden generischen Typ definiert sind:
Box stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // Warnung: unbeaufsichtigter Aufruf von set(T)
Die Warnung zeigt, dass Rohdatentypen generische Typenprüfungen umgehen und das Einfangen von unsicherem Code auf Laufzeit verschieben. Daher sollten Sie Rohdatentypen vermeiden.
Der Abschnitt zu Typlöschung enthält weitere Informationen darüber, wie der Java-Compiler Rohdatentypen verwendet.
Wie bereits erwähnt, wenn Legacy-Code mit generischem Code gemischt wird, können Sie Warnmeldungen ähnlich wie folgende erhalten:
Hinweis: Example.java verwendet unbeaufsichtigte oder unsichere Operationen.
Hinweis: Kompilieren Sie mit -Xlint:unchecked für Details erneut.
Dies kann passieren, wenn eine ältere API verwendet wird, die mit Rohdatentypen arbeitet, wie im folgenden Beispiel gezeigt:
public class WarningDemo {
public static void main(String[] args){
Box bi;
bi = createBox();
}
static Box createBox(){
return new Box();
}
}
Der Begriff "ungeprüft" bedeutet, dass der Compiler nicht genügend Typinformationen hat, um alle Typüberprüfungen durchzuführen, die zur Sicherstellung der Typsicherheit erforderlich sind. Die "ungeprüfte" Warnung ist standardmäßig deaktiviert, obwohl der Compiler einen Hinweis gibt. Um alle "ungeprüften" Warnungen zu sehen, kompilieren Sie erneut mit -Xlint:unchecked。
Wenn Sie das vorherige Beispiel mit -Xlint:unchecked neu kompilieren, zeigt sich die folgende zusätzliche Information:
WarningDemo.java:4: Warnung: [unchecked] unbeaufsichtigte Umwandlung
gefunden : Box
erforderlich: Box
bi = createBox();
^
1 warning
Um ungeprüfte Warnungen vollständig zu deaktivieren, verwenden Sie den -Xlint:-unchecked-Flag. Die @SuppressWarnings("unchecked")
-Annotation unterdrückt ungeprüfte Warnungen. Wenn Sie mit der Syntax von @SuppressWarnings
nicht vertraut sind, siehe Annotations.
Originalquelle: Java Tutorials
Ein "raw" Typ in Java ist eine Klasse, die nicht generisch ist und mit "raw" Objekten umgeht, anstatt mit typsicheren generischen Typ-Parametern.
Zum Beispiel, bevor Java Generics verfügbar war, würden Sie eine Sammlungsklasse wie diese benutzen:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);
Wenn Sie Ihr Objekt zur Liste hinzufügen, ist es unerheblich, um welchen Objekttyp es sich handelt. Und wenn Sie es aus der Liste erhalten, müssen Sie es explizit in den erwarteten Typ umwandeln.
Mit Generics entfernen Sie den "unbekannten" Faktor, weil Sie explizit angeben müssen, welche Art von Objekten in die Liste eingefügt werden können:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = list.get(0);
Bemerken Sie, dass Sie mit Generics das Objekt, das von der get-Methode zurückkommt, nicht umwandeln müssen. Die Sammlung ist vordefiniert, um nur mit MyObject zu arbeiten. Dieser Fakt ist der Hauptgrund für Generics. Es verwandelt eine Quelle von Laufzeitfehlern in etwas, das zur Kompilierzeit überprüft werden kann.
Genauer gesagt ist ein Rohdatentyp das, was Sie erhalten, wenn Sie die Typparameter für einen generischen Typ einfach weglassen. Rohdatentypen waren eigentlich immer nur eine Rückwärtskompatibilitätsfunktion und könnten möglicherweise entfernt werden. Ein ähnliches Verhalten können Sie mit Wildcard-Parametern erhalten.
private static List list = new ArrayList();
Sie sollten den Typ-Parameter angeben.
Die Warnung besagt, dass Typen, die für die Unterstützung von Generics definiert sind, parametrisiert werden sollten, anstatt ihre Rohform zu verwenden.
List
ist so definiert, dass es Generics unterstützt: public class List
. Dies ermöglicht viele typsichere Operationen, die zur Compile-Zeit überprüft werden.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
0 Stimmen
Die Java-Tutorials verwenden immer noch das JComboBox, das diese Warnung verursacht. Welche Version des JComboBox wird diese Warnung nicht verursachen? docs.oracle.com/javase/tutorial/uiswing/components/…
5 Stimmen
Beachten Sie, dass es die sogenannten Raw-Typen nur aus Gründen der Rückwärtskompatibilität mit Java 1.4 und älteren Versionen gibt, die überhaupt keine Generics hatten.