Eine Einführung in generische Typen in Java: Kovarianz und Kontravarianz

Typen

Java ist eine statisch typisierte Sprache. Dies bedeutet, dass Sie zuerst eine Variable und ihren Typ deklarieren müssen, bevor Sie sie verwenden können.

Zum Beispiel: int myInteger = 42;

Geben Sie generische Typen ein.

Generische Typen

Definition: "Ein generischer Typ ist eine generische Klasse oder Schnittstelle, die über Typen parametrisiert wird."

Im Allgemeinen können Sie mit generischen Typen eine allgemeine, generische Klasse (oder Methode) schreiben, die mit verschiedenen Typen arbeitet und die Wiederverwendung von Code ermöglicht.

Anstatt anzugeben obj, dass es sich um einen intTyp, einen StringTyp oder einen anderen Typ handelt, definieren Sie die BoxKlasse, die einen Typparameter akzeptiert: <T>. Dann können Sie nT verwenden, um diesen generischen Typ in einem beliebigen Teil Ihrer Klasse darzustellen.

Geben Sie nun Kovarianz und Kontravarianz ein.

Kovarianz und Kontravarianz

Definition

Varianz bezieht sich darauf, wie sich die Subtypisierung zwischen komplexeren Typen auf die Subtypisierung zwischen ihren Komponenten (Quelle) bezieht.

Eine leicht zu merkende (und äußerst informelle) Definition von Kovarianz und Kontravarianz lautet:

  • Kovarianz: Akzeptieren Sie Subtypen
  • Kontravarianz: Akzeptiere Supertypen

Arrays

In Java sind Arrays kovariant , was zwei Auswirkungen hat.

Erstens kann ein Array vom Typ T[]Elemente vom Typ Tund seine Untertypen enthalten.

Number[] nums = new Number[5];nums[0] = new Integer(1); // Oknums[1] = new Double(2.0); // Ok

Zweitens ist ein Array vom Typ S[]ein Subtyp von T[]if Sist ein Subtyp von T.

Integer[] intArr = new Integer[5];Number[] numArr = intArr; // Ok

Es ist jedoch wichtig zu beachten, dass: (1) numArreine Referenz des Referenztyps Number[]auf das „tatsächliche Objekt“ intArrdes „tatsächlichen Typs“ ist Integer[].

Daher wird die folgende Zeile problemlos kompiliert, erzeugt jedoch eine Laufzeit ArrayStoreException(aufgrund der Heap-Verschmutzung):

numArr[0] = 1.23; // Not ok

Es wird eine Laufzeitausnahme erzeugt, da Java zur Laufzeit weiß, dass das „tatsächliche Objekt“ intArrtatsächlich ein Array von ist Integer.

Generika

Bei generischen Typen kann Java zur Laufzeit die Typinformationen der Typparameter aufgrund der Typlöschung nicht kennen. Daher kann es zur Laufzeit nicht vor Haufenverschmutzung schützen.

Generika sind daher unveränderlich.

ArrayList intArrList = new ArrayList();ArrayList numArrList = intArrList; // Not okArrayList anotherIntArrList = intArrList; // Ok

Die Typparameter müssen genau übereinstimmen, um vor Haufenverschmutzung zu schützen.

Aber geben Sie Platzhalter ein.

Platzhalter, Kovarianz und Kontravarianz

Mit Platzhaltern können Generika Kovarianz und Kontravarianz unterstützen.

Wenn wir das vorherige Beispiel optimieren, erhalten wir dies, was funktioniert!

ArrayList intArrList = new ArrayList();ArrayList numArrList = intArrList; // Ok

Das Fragezeichen "?" bezieht sich auf einen Platzhalter, der einen unbekannten Typ darstellt. Es kann eine Untergrenze haben, wodurch der unbekannte Typ auf einen bestimmten Typ oder dessen Supertyp beschränkt wird.

Daher wird in Zeile 2 ? super Integer"jeder Typ, der ein Integer-Typ oder sein Supertyp ist" übersetzt.

Sie können den Platzhalter auch über die Obergrenze setzen, wodurch der unbekannte Typ auf einen bestimmten Typ oder dessen Subtyp beschränkt wird ? extends Integer.

Schreibgeschützt und schreibgeschützt

Kovarianz und Kontravarianz führen zu einigen interessanten Ergebnissen. Kovariante Typen sind schreibgeschützt, während kontravariante Typen schreibgeschützt sind.

Denken Sie daran, dass kovariante Typen Untertypen akzeptieren ArrayLister> can contain any object that is either of a Number type or its subtype.

In this example, line 9 works, because we can be certain that whatever we get from the ArrayList can be upcasted to a Number type (because if it extends Number, by definition, it is a Number).

But nums.add() doesn’t work, because we cannot be sure of the “actual type” of the object. All we know is that it must be a Number or its subtypes (e.g. Integer, Double, Long, etc.).

With contravariance, the converse is true.

Line 9 works, because we can be certain that whatever the “actual type” of the object is, it must be Integer or its supertype, and thus accept an Integer object.

But line 10 doesn’t work, because we cannot be sure that we will get an Integer. For instance, nums could be referencing an ArrayList of Objects.

Applications

Therefore, since covariant types are read-only and contravariant types are write-only (loosely speaking), we can derive the following rule of thumb: “Producer extends, consumer super”.

A producer-like object that produces objects of type T can be of type parameter T>, while a consumer-like object that consumes objects oftype T can be of type parameter super T>.

Original text