820 Stimmen

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

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?

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.

889voto

polygenelubricants Punkte 362173

Was ist ein Rohtyp?

Die Java Sprachspezifikation definiert eine Rohtyp wie folgt:

JLS 4.8 Rohtypen

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-Typs R die nicht von einer Oberklasse oder einem Superinterface von R .

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.


Was ist so besonders an rohen Sorten?

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.

Siehe auch


Was ist der Unterschied zwischen einem rohen Typ und der Verwendung von <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 Typ List<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 eine List<String> auf einen Parameter des Typs List können Sie ihn nicht an einen Parameter des Typs List<Object> . Es gibt Subtypisierungsregeln für Generika, und List<String> ist ein Subtyp des Rohtyps List aber nicht von dem parametrisierten Typ List<Object> . Daraus folgt, verlieren Sie die Typsicherheit, wenn Sie rohe Typen wie List verwenden, aber nicht, wenn Sie einen parametrisierten Typ wie List<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.

Siehe auch


Was ist der Unterschied zwischen einem rohen Typ und der Verwendung von <?> 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 .


Ein Rohtyp ist die Löschung dieses Typs

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 Rohtyps C der nicht von seinen Oberklassen oder Superschnittstellen geerbt wird, ist der Rohtyp, der der Löschung seines Typs in der generischen Deklaration entspricht, die der C .

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

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


Wenn es unsicher ist, warum ist es dann erlaubt, einen Rohtyp zu verwenden?

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 .


Gibt es keine Ausnahmen?

Da Java-Generics nicht reifiziert 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

25 Stimmen

Was meinst du damit, dass "Java-Generika nicht reifiziert sind"?

8 Stimmen

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

0 Stimmen

Kannst du mir ein Beispiel für "Migrationskompatibilität" geben?

70voto

josefx Punkte 15196

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:

  • Jedes Object, das in einer Collection gespeichert wird, muss vor der Verwendung umgewandelt werden
  • Generics ermöglichen Kompilierungszeitprüfungen
  • Die Verwendung von Rohdatentypen entspricht dem Speichern jedes Werts als Object

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

32voto

Adelin Punkte 16254

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

Ungeprüfte Fehlermeldungen

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

31voto

Andy White Punkte 83877

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.

4 Stimmen

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.

0 Stimmen

@zerocrates: ähnlich aber anders! Die Verwendung von ? bietet immer noch Typsicherheit. Ich habe es in meiner Antwort behandelt.

20voto

Bozho Punkte 570413
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.

3 Stimmen

Jetzt ersetzt durch Diamant-Inferenz in Java 7 -- private static List list = new ArrayList<>();

CodeJaeger.com

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.

Powered by:

X