Kovarianz, Invarianz und Kontravarianz erklärt in einfachem Englisch?

Heute lese ich einige Artikel über Covariance, Contravariance (und Invariance) in Java. Ich lese den englischen und deutschen Wikipedia-Artikel und einige andere Blogposts und Artikel von IBM.

Aber ich bin immer noch ein wenig verwirrt darüber, worum es bei diesen genau geht? Einige sagen, es geht um die Beziehung zwischen Typen und Subtypen, einige sagen, es geht um Typkonvertierung und einige sagen, dass es verwendet wird, um zu entscheiden, ob eine Methode überschrieben oder überladen wird.

Also suche ich nach einer einfachen Erklärung in einfachem Englisch, die einem Anfänger zeigt, was Covariance und Contravariance (und Invariance) ist. Pluspunkt für ein einfaches Beispiel.

Manche sagen, es geht um die Beziehung zwischen Typen und Subtypen, andere sagen, es geht um Typkonvertierung und andere sagen, es wird verwendet, um zu entscheiden, ob eine Methode überschrieben oder überladen wird.

Alles das oben Genannte.

Im Grunde beschreiben diese Begriffe, wie die Subtyprelation durch Typumwandlungen beeinflusst wird. Das heißt, wenn A und B Typen sind, ist f eine Typumwandlung und ≤ die Subtyprelation (dh A ≤ B bedeutet, dass A ein Subtyp von B ), haben wir

  • f ist kovariant, wenn A ≤ B bedeutet, dass f(A) ≤ f(B)
  • f ist kontravariant, wenn A ≤ B bedeutet, dass f(B) ≤ f(A)
  • f ist invariant, wenn keine der obigen Aussagen zutrifft

Betrachten wir ein Beispiel. Sei f(A) = List wobei List durch deklariert wird

 class List { ... } 

Ist f kovariant, kontravariant oder invariant? Covariant würde bedeuten, dass eine List ein Subtyp von List , eine Kontravariante, dass ein List ein Subtyp von List und Invariant ist, der kein Subtyp des anderen ist, dh List und List sind nicht konvertierbare Typen. In Java ist letzteres richtig, wir sagen (etwas informell), dass Generika invariant sind.

Ein anderes Beispiel. Sei f(A) = A[] . Ist f kovariant, kontravariant oder invariant? Das heißt, ist String [] ein Subtyp von Object [], Object [] ein Subtyp von String [], oder ist es kein Subtyp des anderen? (Antwort: In Java sind Arrays kovariant)

Das war noch ziemlich abstrakt. Um es konkreter zu machen, schauen wir uns an, welche Operationen in Java in Bezug auf die Subtyprelation definiert sind. Das einfachste Beispiel ist die Zuweisung. Die Aussage

 x = y; 

kompiliert nur, wenn typeof(y) ≤ typeof(x) . Das heißt, wir haben gerade die Aussagen gelernt

 ArrayList strings = new ArrayList(); ArrayList objects = new ArrayList(); 

wird nicht in Java kompilieren, aber

 Object[] objects = new String[1]; 

werden.

Ein anderes Beispiel, bei dem die Untertypbeziehung von Bedeutung ist, ist ein Methodenaufruf-Ausdruck:

 result = method(a); 

Informell gesprochen wird diese statement ausgewertet, indem Sie dem ersten Parameter der Methode den Wert von a zuweisen, dann den Hauptteil der Methode ausführen und dann den Rückgabewert der Methode result zuweisen. Wie die einfache Zuweisung im letzten Beispiel muss die “rechte Seite” ein Subtyp der “linken Seite” sein, dh diese Aussage kann nur gültig sein, wenn typeof(a) ≤ typeof(parameter(method)) und returntype(method) ≤ typeof(result) . Das heißt, wenn die Methode wie folgt deklariert wird:

 Number[] method(ArrayList list) { ... } 

Keiner der folgenden Ausdrücke wird kompiliert:

 Integer[] result = method(new ArrayList()); Number[] result = method(new ArrayList()); Object[] result = method(new ArrayList()); 

aber

 Number[] result = method(new ArrayList()); Object[] result = method(new ArrayList()); 

werden.

Ein weiteres Beispiel, bei dem Subtyping-Angelegenheiten Vorrang haben. Erwägen:

 Super sup = new Sub(); Number n = sup.method(1); 

woher

 class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Number method(Number n); } 

Informell wird die Laufzeit dies umschreiben auf:

 class Super { Number method(Number n) { if (this instanceof Sub) { return ((Sub) this).method(n); // * } else { ... } } } 

Damit die markierte Zeile kompiliert werden kann, muss der Methodenparameter der überschreibenden Methode ein Supertyp des Methodenparameters der überschriebenen Methode und der Rückgabetyp ein Subtyp der überschriebenen Methode sein. Formal muss f(A) = parametertype(method asdeclaredin(A)) zumindest kontravariant sein, und wenn f(A) = returntype(method asdeclaredin(A)) muss mindestens kovariant sein.

Beachten Sie die “mindestens” oben. Dies sind Mindestanforderungen, die jede vernünftige statisch typsichere objektorientierte Programmiersprache erzwingen wird, aber eine Programmiersprache kann strikter sein. Im Fall von Java 1.4 müssen Parametertypen und Rückgabetypen für Methoden beim Überschreiben von Methoden identisch sein, dh parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) beim Überschreiben. Seit Java 1.5 sind kovariante Rückgabetypen beim Überschreiben erlaubt, dh das Folgende wird in Java 1.5 kompiliert, nicht jedoch in Java 1.4:

 class Collection { Iterator iterator() { ... } } class List extends Collection { @Override ListIterator iterator() { ... } } 

Ich hoffe, ich habe alles abgedeckt – oder besser gesagt, an der Oberfläche gekratzt. Dennoch hoffe ich, dass es hilft, das abstrakte, aber wichtige Konzept der Typvarianz zu verstehen.

Nehmen wir das Java-System und dann die classn:

Jedes Objekt eines Typs T kann durch ein Objekt des Subtyps von T ersetzt werden.

TYPVARIANZ – KLASSENMETHODEN HABEN FOLGENDE FOLGEN

 class A { public S f(U u) { ... } } class B extends A { @Override public T f(V v) { ... } } B b = new B(); t = bf(v); A a = ...; // Might have type B s = af(u); // and then do V v = u; 

Man kann sehen, dass:

  • Das T muss Subtyp S sein ( kovariant, da B Subtyp von A ist ).
  • Das V muss Supertyp von U sein ( kontravariant , als kontra inheritancesrichtung).

Jetzt kann Co und Kontra sich darauf beziehen, dass B der Subtyp von A ist. Die folgenden stärkeren Typisierungen können mit spezifischerem Wissen eingeführt werden. Im Subtyp.

Covariance (verfügbar in Java) ist nützlich, um zu sagen, dass man ein spezifischeres Ergebnis im Subtyp zurückgibt; besonders gesehen, wenn A = T und B = S. Contravariance sagt, dass Sie bereit sind, mit einem allgemeineren Argument umzugehen.