909 Stimmen

Ist List<Dog> eine Unterklasse von List<Animal>? Warum sind Java Generics nicht implizit polymorph?

Ich bin ein wenig verwirrt darüber, wie Java Generika behandeln Vererbung / Polymorphismus.

Nehmen Sie die folgende Hierarchie an -

Tier (Elternteil)

Hund - Katze (Kinder)

Nehmen wir also an, ich habe eine Methode doSomething(List<Animal> animals) . Nach allen Regeln der Vererbung und des Polymorphismus würde ich annehmen, dass ein List<Dog> ist a List<Animal> et un List<Cat> ist a List<Animal> - und kann daher an diese Methode übergeben werden. Dem ist nicht so. Wenn ich dieses Verhalten erreichen will, muss ich der Methode explizit sagen, dass sie eine Liste einer beliebigen Unterklasse von Animal akzeptieren soll, indem ich sage doSomething(List<? extends Animal> animals) .

Ich verstehe, dass dies das Verhalten von Java ist. Meine Frage ist pourquoi ? Warum ist Polymorphismus im Allgemeinen implizit, aber wenn es um Generika geht, muss er angegeben werden?

22 Stimmen

Und eine völlig unabhängige Grammatikfrage, die mich gerade beschäftigt - sollte mein Titel "warum" lauten? sind nicht Javas Generika" oder "Warum ist nicht Java's generics"? Ist "Generika" Plural wegen des s oder Singular, weil es eine Einheit ist?

28 Stimmen

Generika, wie sie in Java verwendet werden, sind eine sehr schlechte Form des parametrischen Polymorphismus. Setzen Sie nicht zu viel Vertrauen in sie (wie ich es früher getan habe), denn eines Tages werden Sie hart an ihre erbärmlichen Grenzen stoßen: Surgeon extends Handable<Scalpel>, Handable<Sponge> KABOOM! Macht nicht berechnen [TM]. Das ist die Einschränkung der Java-Generik. Jede OOA/OOD kann gut in Java übersetzt werden (und MI kann sehr schön mit Java-Schnittstellen gemacht werden), aber Generika sind einfach nicht geeignet. Sie sind gut für "Sammlungen" und prozedurale Programmierung, dass sagte (das ist, was die meisten Java-Programmierer sowieso tun, so...).

8 Stimmen

Die Superklasse von List<Dog> ist nicht List<Animal>, sondern List<?> (d. h. eine Liste unbekannten Typs). Generics löscht Typinformationen im kompilierten Code. Dies geschieht, damit Code, der Generics verwendet (Java 5 und höher), mit früheren Versionen von Java ohne Generics kompatibel ist.

1064voto

Jon Skeet Punkte 1325502

Nein, ein List<Dog> es nicht a List<Animal> . Überlegen Sie, was Sie mit einer List<Animal> - können Sie hinzufügen cualquier Tier dazu... einschließlich einer Katze. Kann man nun logischerweise eine Katze zu einem Wurf von Welpen hinzufügen? Auf keinen Fall.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

Plötzlich haben Sie eine muy verwirrte Katze.

Nun, Sie kann nicht einfügen Cat zu einer List<? extends Animal> weil Sie nicht wissen, dass es ein List<Cat> . Sie können einen Wert abrufen und wissen, dass es sich um einen Animal aber man kann nicht beliebige Tiere hinzufügen. Das Gegenteil ist der Fall bei List<? super Animal> - In diesem Fall können Sie eine Animal aber Sie wissen nicht, was sich daraus ableiten lässt, denn es könnte sich um eine List<Object> .

111voto

Michael Ekstrand Punkte 27071

Das, wonach Sie suchen, heißt _kovarianter Typ Parameter_ . Das bedeutet, dass, wenn ein Objekttyp in einer Methode durch einen anderen ersetzt werden kann (z. B., Animal kann ersetzt werden durch Dog ), gilt dasselbe für Ausdrücke, die diese Objekte verwenden (also List<Animal> könnte ersetzt werden durch List<Dog> ). Das Problem ist, dass die Kovarianz für veränderliche Listen im Allgemeinen nicht sicher ist. Angenommen, Sie haben eine List<Dog> und es wird als ein List<Animal> . Was passiert, wenn Sie versuchen, eine Katze zu dieser List<Animal> die eigentlich eine List<Dog> ? Wenn man automatisch zulässt, dass Typparameter kovariant sind, bricht das Typsystem.

Es wäre sinnvoll, eine Syntax hinzuzufügen, die es erlaubt, Typparameter als kovariant anzugeben, wodurch die ? extends Foo in den Methodendeklarationen zu verwenden, was jedoch zu zusätzlicher Komplexität führt.

51voto

Michael Aaron Safyan Punkte 90663

Der Grund für eine List<Dog> ist keine List<Animal> ist, dass Sie zum Beispiel eine Cat in eine List<Animal> , aber nicht in eine List<Dog> ... Sie können Wildcards verwenden, um Generika, wo möglich, erweiterbar zu machen; zum Beispiel kann das Lesen aus einer List<Dog> ist ähnlich wie das Lesen aus einer List<Animal> -- aber nicht das Schreiben.

En Generika in der Sprache Java und die Abschnitt über Generics aus den Java-Tutorials haben eine sehr gute, ausführliche Erklärung, warum manche Dinge polymorph oder mit Generika erlaubt sind oder nicht.

50voto

einpoklum Punkte 100527

Ein Punkt, der meiner Meinung nach hinzugefügt werden sollte, ist andere antwortet zu erwähnen ist, dass während

List<Dog> ist nicht List<Animal> in Java

es ist auch wahr, dass

Eine Liste von Hunden ist eine Liste von Tieren auf Englisch (bei vernünftiger Auslegung)

Die Intuition des Auftraggebers - die natürlich völlig richtig ist - ist der letzte Satz. Wenn wir jedoch diese Intuition anwenden, erhalten wir eine Sprache, die in ihrem Typsystem nicht Java-ähnlich ist: Angenommen, unsere Sprache erlaubt es, eine Katze zu unserer Liste von Hunden hinzuzufügen. Was würde das bedeuten? Es würde bedeuten, dass die Liste aufhört, eine Liste von Hunden zu sein, und lediglich eine Liste von Tieren bleibt. Und eine Liste von Säugetieren und eine Liste von Vierfüßlern.

Mit anderen Worten: A List<Dog> in Java bedeutet im Englischen nicht "eine Liste von Hunden", sondern "eine Liste von Hunden und nichts anderem als Hunden".

Allgemeiner ausgedrückt, Die Intuition von OP eignet sich für eine Sprache, in der Operationen auf Objekten deren Typ ändern können bzw. die Art(en) eines Objekts sind eine (dynamische) Funktion seines Werts.

38voto

Yishai Punkte 87548

Ich würde sagen, der ganze Sinn von Generics ist, dass sie das nicht zulassen. Betrachten Sie die Situation mit Arrays, die diese Art von Kovarianz zulassen:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

Dieser Code lässt sich gut kompilieren, führt aber zu einem Laufzeitfehler ( java.lang.ArrayStoreException: java.lang.Boolean in der zweiten Zeile). Sie ist nicht typsicher. Der Sinn von Generics ist es, die Kompilierzeit-Typsicherheit hinzuzufügen, andernfalls könnte man einfach bei einer einfachen Klasse ohne Generics bleiben.

Nun gibt es Zeiten, in denen man flexibler sein muss, und das ist es, was die ? super Class y ? extends Class sind für. Ersteres ist, wenn Sie in einen Typ einfügen müssen Collection (z. B.), und letzteres ist für den Fall, dass Sie auf typsichere Weise aus ihm lesen müssen. Die einzige Möglichkeit, beides gleichzeitig zu tun, besteht darin, einen bestimmten Typ zu haben.

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