Advanced Java Services | Kovarianz, Kontravarianz und Invarianz |
Für das bessere Verständnis dieser Begriffe wiederholen wir kurz die Zuweisungskompatibilität von primitiven Datentypen und Referenztypen mit einigen typischen Beispielen.
Bekannt ist folgendes (siehe auch Implizite Datentypanpassung).
short a = 17; int b = a; // implizit kompatibel = kein cast notwendig int c = 71; short d = (short)c; // explizit kompatibel = cast notwendig boolean boo = true; //int x = boo; // nicht kompatibel
Eine Zuweisung an einen breiteren Datentyp ist ohne cast möglich, für die umgekehrte Richtung ist ein expliziter cast notwendig. Der Datentyp boolean ist nicht kompatibel zu anderen primitiven Datentypen.
Für Referenzen gelten etwas andere Regeln. Hier entsteht Kompatibilität durch Vererbung.
String st = "Hallo"; Object ob = st; // (upcast) implizit kompatibel = kein cast notwendig CharSequence cs = st; // implizit kompatibel = kein cast notwendig st = (String)cs ; // (downcast) explizit kompatibel = cast notwendig //StringBuilder sb = st; // nicht kompatibel
Eine Zuweisung an einen Elterntyp (es gibt nur einen) ist ohne cast möglich, für die umgekehrte Richtung ist ein expliziter cast notwendig. Referenztypen, die nicht in direkter Vererbung stehen sind nicht kompatibel.
Zwischen primitiven und nichtprimitiven Datentypen gibt es keine Kompatibilität.
String s = "hi"; //int y = s; // nicht kompatibel int y = 17; //s = y; // nicht kompatibel
Durch Autoboxing bzw. Autounboxing entsteht jedoch etwas, was wir hier Quasikompatibilität nennen wollen.
// autoboxing int e = 17; Integer in = e; // shortcut für: in = new Integer(e); // auto-unboxing int f = in; // shortcut für: f = in.intValue();
Wir haben also Grunddatentypen wie int, long , etc. (primitive Datentypen) und String, StringBuilder etc. (nichtprimitive Datentypen) und bei diesen die Zuweisungskompatibilität als Verwandtschaftsbeziehung.
Von diesen Datentypen leiten sich weitere Datentypen ab und sind somit abhängigge Datentypen. Dies sind zum einen Arrays als echte Datentypen, also z.Bsp. int[], long[], String[] usw. und zum anderen generische Datentypen wie List<T>, HashSet<T>, MyClass<T> usw.
Die Frage nach der Varianz ist nun die Frage, inwiefern sich evtl. Zuweisungskompatibiltäten der Grunddatentypen auf die abgeleiteten Datentypen übertragen. Man unterscheidet dabei drei Fälle.
Varianztyp | Bedeutung |
---|---|
Kovarianz | Die Verwandtschaft der Grunddatentypen überträgt sich in der gleichen Richtung auf die abgeleiteten Typen |
Kontravarianz | Die Verwandtschaft der Grunddatentypen überträgt sich in der umgekehrten Richtung auf die abgeleiteten Typen |
Invarianz | Die Verwandtschaft der Grunddatentypen überträgt sich in keiner Richtung auf die abgeleiteten Typen |
Wie übertragen sich diese Kompatibilitäten auf Arrays und generische Typen? Diese Frage führt uns zu den verschiedenen Varianzbegriffen. Wir beginnen mit primitiven Arrays.
Ein typisches Beispiel zeigt das Verhalten.
int[] iArr = new int[17]; //double[] dArr = iArr; nicht kompatibel double[] dArr = new double[17]; //int[] arr = dArr; // nicht kompatibel
Die Verwandtschaft zwischen int und double überträgt sich also nicht auf Arrays. Dieses Verhalten heißt invariant.
Arrays von Referenzen verhalten sich anders. Hier sind casts und upcasts möglich.
String[] sArr = { "foo", "bar"}; Object[] oArr = sArr; // impliziter upcast = implizit kompatibel = (implizit) kovariant String[] stArr = (String[])obArr; // expliziter downcast = explizit kompatibel = (explizit) kontravariant
Die "natürliche" Verwandtschaft von Referenzen entsteht durch Vererbung. Überträgt sich diese Verwandtschaft auf Arrays, so spricht man von Kovarianz, gibt es dagegen eine Verwandtschaft in umgekehrtewr Richtung, so nennt man das kontravariant. Arrays von Referenzen sind also implizit kovariant und explizit kontravariant, wenn die zugrunde liegenden Typen verwandt sind.
Bei einem expliziten downcast prüft der Compiler nicht, ob dieser zulässig ist. Dies geschieht erst zur Laufzeit durch die JVM. Diese Prüfungen sind ein relativ hoher Aufwand und kosten Performance. Eine Typverletzung hier führt dann zu einer ClassCastException. Hierzu das folgende kleine Beispiel:
Object[] obArr = { "foo", new StringBuilder("bar") }; String[] stArr = (String[])obArr; // expliziter downcast // JVM führt den downcast in einer Schleife durch //for (int i=0; i < obArr.length; i++) //{ // stArr[i] = (String)obArr[i]; //} // führt zu ClassCastException
Hier unterschieden wir mehrere Fälle.
Wenn der Typparameter gleich bleibt überträgt sich die Verwandtschaft der Grundtypen.
List l = new ArrayList(); // und List<String> sList = new ArrayList<>(); //
Dies gilt auch für Parameter mit ?-Platzhalter.
Hierzu reicht ein Beispiel:
// ohne ? Platzhalter List<Object> oList = new ArrayList<>(); List<String> sList = new ArrayList<>(); //oList = sList; // nicht kompatibel //sList = oList; // nicht kompatibel
Zur Erinnerung: <? extends X> macht die Collection readonly!
List<? extends Object> obList ; // ? = shortcut für ? extends Object, ? extends bedeutet readonly List<String> stList = new ArrayList<>(); obList = stList ; // impliziter upcast möglich = implizit kompatibel = implizit kovariant //stList = obList; // expliziter downcast nicht möglich = NICHT explizit kompatibel = nicht kontravariant List<? extends Number> stList2 = new ArrayList<>(); obList = stList2;
Hier überträgt sich also die Verwandtschaft durch Vererbung, es liegt Kovarianz vor.
List<? super Integer> supIntList = new ArrayList<>(); List<Object> objList; //objList = supList; // umpliziter upcast nicht möglich = NICHT implizit kompatibel = nicht kovariant objList = (List<Object>)supIntList; // expliziter downcast = explizit kompatibel = explizit kontravariant
Hier ergibt sich eine Verwandtschaft umgekehrt zur Verwandtschaft durch Vererbung, es liegt also Kontravarianz vor.
Zur Erinnerung: Im Falle <? super X> besteht Schreibzugriff nur mit Basistyp X.
List<? super Integer> supIntList = new ArrayList<>(); int i = 17 ; supIntList.add(i); // autoboxing ! // aber Double d = 71; Number num = d; // supList.add(num); compiletime error