Generics und Polymorphie
Auch mit generischen Klassen stehen die Mechanismen Vererbung und Überladen zur Verfügung. Dabei muss
aber beachtet werden, dass generische Klassen sich "invariant" verhalten: Der Typ selbst folgt der
Vererbungsbeziehung, eine Vererbung des Typ-Parameters begründet keine Vererbungsbeziehung! D.h.
aus U extends O
folgt nicht A<U> extends A<O>
.
Bei Arrays ist es genau anders herum: Wenn U extends O
dann gilt auch U[] extends O[]
... (Dies
nennt man "kovariantes" Verhalten.)
- (K3) Vererbungsbeziehungen mit generischen Klassen
- (K3) Umgang mit Arrays und generischen Typen
Generische Polymorphie
B<E> extends A<E>
class A<E> { ... }
class B<E> extends A<E> { ... }
A<Double> ad = new B<Double>();
A<String> as = new B<String>();
class Vector<E> { ... }
class Stack<E> extends Vector<E> { ... }
Vector<Double> vd = new Stack<Double>();
Vector<String> vs = new Stack<String>();
=> Polymorphie bei Generics bezieht sich auf Typ (nicht Typ-Parameter)
Invarianz: Generics sind invariant, d.h. ein HashSet<String>
ist ein
Untertyp von Set<String>
. Bei der Vererbung muss der Typ-Parameter identisch
sein.
Polymorphie bei Generics bezieht sich nur auf Typ!
"B extends A
" bedeutet nicht "C<B> extends C<A>
"
Stack<Number> s = new Stack<Integer>(); // DAS GEHT SO NICHT!
// Folgen (wenn obiges gehen wuerde):
s.push(new Integer(3)); // das ginge sowieso ...
// Folgen (wenn obiges gehen wuerde):
// Stack<Number> waere Oberklasse auch von Stack<Double>
s.push(new Double(2.0)); // waere dann auch erlaubt ...
// Das Objekt (Stack<Integer>) kann aber keine Double speichern!
// Zur Laufzeit keine Typ-Informationen mehr!
- Typ-Löschung => zur Laufzeit keine Typinformationen vorhanden
- Compiler muss Typen prüfen (können)!
Abgrenzung: Polymorphie bei Arrays
Wenn "B extends A
" dann "B[] extends A[]
"
Object[] x = new String[] {"Hello", "World", ":-)"};
x[0] = "Hallo";
x[0] = new Double(2.0); // Laufzeitfehler
String[] y = x; // String[] ist KEIN Object[]!!!
- Arrays besitzen Typinformationen über gespeicherte Elemente
- Prüfung auf Typ-Kompatibilität zur Laufzeit (nicht Kompilierzeit!)
Arrays gab es sehr früh, Generics erst relativ spät (ab Java6) => bei Arrays fand man das Verhalten natürlich und pragmatisch (trotz der Laufzeit-Überprüfung).
Bei der Einführung von Generics musste man Kompatibilität sicherstellen (alter Code soll auch mit neuen Compilern übersetzt werden können - obwohl im alten Code Raw-Types verwendet werden). Außerdem wollte man von Laufzeit-Prüfung hin zu Compiler-Prüfung. Da würde das von Arrays bekannte Verhalten Probleme machen ...
Kovarianz: Arrays sind kovariant, d.h. ein Array vom Typ String[]
ist wegen
String extends Object
ein Untertyp von Object[]
.
Arrays vs. parametrisierte Klassen
=> Keine Arrays mit parametrisierten Klassen!
Foo<String>[] x = new Foo<String>[2]; // Compilerfehler
Foo<String[]> y = new Foo<String[]>(); // OK :)
Arrays mit parametrisierten Klassen sind nicht erlaubt! Arrays brauchen zur Laufzeit Typinformationen, die aber durch die Typ-Löschung entfernt werden.
Diskussion Vererbung vs. Generics
Vererbung:
- IS-A-Beziehung
- Anwendung: Vererbungsbeziehung vorliegend, Eigenschaften verfeinern
- Beispiel: Ein Student ist eine Person
Generics:
- Schablone (Template) für viele Datentypen
- Anwendung: Identischer Code für unterschiedliche Typen
- Beispiel: Datenstrukturen, Algorithmen generisch realisieren
Wrap-Up
-
Generics: Vererbung und Überladen möglich, aber: Aus "
U extends O
" folgt nicht "A<U> extends A<O>
" -
Achtung: Bei Arrays gilt aber: Wenn "
U extends O
" dann gilt auch "U[] extends O[]
" ...
- [Bloch2018] Effective Java
Bloch, J., Addison-Wesley, 2018. ISBN 978-0-13-468599-1. - [Java-SE-Tutorial] The Java Tutorials
Oracle Corporation, 2022.
Specialized Trails: Generics - [LernJava] Learn Java
Oracle Corporation, 2022.
Kapitel Generics - [Ullenboom2021] Java ist auch eine Insel
Ullenboom, C., Rheinwerk-Verlag, 2021. ISBN 978-3-8362-8745-6.
Kapitel 11.5