„Definition“ Kovarianz
Ein Objekt Foo<T> ist kovariant, falls Foo<Derived> Unterklasse von Foo<Base> ist, also genau dann, wenn überall da wo Foo<Base> steht auch Foo<Drived> stehen kann.
Positives Beispiel
Foo<T> sei der Rückgabwert einer method(): T. Da der Klient nur Methoden von T aufrufen kann, kann man auch abgeleitete Klassen zurückgeben: da wo Foo<Base> steht kann auch Foo<Drived> stehen. Rückgabewerte von Methoden sind kovariant.
Bis JDK 1.4 war es nicht möglich, dass es bei einer Basisklasse mit Methode
method() : Base
eine Subklasse geben kann mit
method(): Sub.
Erst durch die Einführung von Kovarianz bei Rückgabewerten mit JAVA 5 war das erlaubt. Vormals musste bei der Nutzung gecastet werden
Sub sub = (Sub) sub.method();
Technisch findet ein Überladen statt, da für ein Überschreiben die Methodensignaturen gleich sein müssen. Daher existieren in Methoden-Fall beide Methoden method():Base und method():Sub.
Negatives Beispiel
Stack<T> ist nicht kovariant: String (sub) ist eine Unterklasse von Objekt (base). Betrachte folgenden Code
(1) Stack<String> strings = createStack(String.class);
(2) Stack<Object> objects = strings; // assume assignment is allowed
(3) objects.push(new Object());
(4) String element = strings.pop();
Wäre Stack<T> kovariant, so könnte man dort wo Stack<base> steht auch Stack<String> stehen. Damit wäre Zeile (2) erlaubt. Das in (3) erzeugte Objekt wäre aber in (4) auch ein String, was falsch ist.
„Definition“ Kontravarianz
Foo<T> ist kontravariant, falls Foo<Base> eine Spezialisierung von Foo<Derived> ist, d.h. genau dann, wenn überall wo Foo<Derived> steht auch Foo<Base> stehen darf.
Positives Beispiel
Foo<T> sei der Eingangsparameter einer methode(in : T). Sei method(in : sub). Da sub als Unterklasse auch ein base ist, kann in einer spezialisierten Methode auch methode(in : base) stehen. Eingangsparameter sind kontravariant.
Negatives Beispiel
Stack<T> ist auch nicht kontravariant: String (sub) ist eine Unterklasse von Objekt (base). Betrachte folgenden Code
(1) Stack<Object> objects = createStack(Object.class);
(2) Stack<String> strings = objects; // assume assignment is allowed
(3) objects.push(new Object());
(4) String element = strings.pop();
Wäre Stack<T> kontravariant, so könnte man dort wo Stack<String> steht auch Stack<Object> stehen. Damit wäre wiederum Zeile (2) erlaubt. Das in (3) erzeugte Objekt wäre aber in (4) auch ein String, was ebenfalls falsch ist.
Arrays sind in JAVA kovariant
Ist Base Baisklasse von Sub, so ist Base[] Basisklasse von Sub[]. Nach dem LSP (Ersetzungsprinzip) kann also überall da, wo Base[] gefordert ist auch Sub[] eingesetzt werden.
Object[] objarray = new String[]{…};
Problematisch hier ist der Laufzeittyp (String). Der Compiler-Typ ist Object. Daher geht man auch davon aus, dass im objarray Integer gespeichert werden könnten. Ist aber zur Laufzeit wegen dem Laufzeittyp nicht möglich.