EIDI-Crashkurs 2020 › Polymorphie mit Gerenics › Antwort auf: Polymorphie mit Gerenics
21.02.2020 um 17:58 Uhr
#3525
Beispiel (kompiliert nicht):
class A<T> {
T t;
void f(T x) { x.m(); }
}
class X { void m() {} }
class Y extends X {}
Beispielobjekte:
X g = new X(); Y h = new Y();
Was gibt es zu beachten?
- Eine generische Klasse gibt es nicht ohne eingesetzten Typ. Es gibt z. B. Objekte der Klassen
A<X>
undA<Y>
undA<String>
, es gibt aber keine Objekte der KlasseA
, weil es die “allgemeine” KlasseA
nicht gibt. AuchA<T>
gibt es nicht, weilT
keine Klasse ist. Der generische TypT
könnte zusätzlich eingeschränkt sein, z. B.class A<T extends X>
, dann wäreA<String>
nicht mehr möglich, weil fürT
dann mindestensX
(oder spezieller, alsoY
geht auch) eingesetzt werdem muss.
- Der generische Typ gibt nicht die Vererbungshierarchie vor. Während die Zuweisung von einem
Y
an eine Variable vom TypX
möglich wäre (d. h.X var = new Y();
geht), daY
spezieller ist alsX
, gilt das nicht für die KlasseA<...>
. Weder die Zuweisung vonA<Y>
anA<X>
noch umgekehrt wäre möglich (d. h.A<X> var = new A<Y>();
ist ein Compiler-Fehler), weil die KlassenA<Y>
undA<X>
überhaupt nichts miteinander zu tun haben.
- Generische Typen sind nur Platzhalter. Angenommen es existiert eine Variable
A<Y> m
, dann gibt es dafür die Methodevoid f(Y x)
(siehe Beispiel oben). Der Aufrufm.f(h)
wäre möglich,m.f(g)
wäre nicht möglich. Für eine VariableA<X> n
wären beide Aufrufe möglich. Für eine VariableA<String> o
wäre keiner der beide Aufrufe möglich.
- Type Erasure: Generische Typen werden nicht als Platzhalter übersetzt, sondern wird beim Kompilieren der allgemeinste Typ verwendet. Vielleicht fragst du dich, warum das Beispiel eigentlich gar nicht kompiliert. Der Grund dafür ist Type Erasure. Der Compiler übersetzt “A” nicht in allen möglichen Formen (
A<X>
,A<Y>
,A<Integer>
, …), weil es dafür ja unendlich viele Möglichkeiten gäbe. Obwohl es die Klasse in unendlich vielen verschiedenen Ausführungen gibt, wird sie nur einmal übersetzt – und zwar alsA<Object>
, d. h.T
wird durchObject
ersetzt. Angenommen wir hätte einA<X> a = new A<X>();
und rufen nun die Methodef
mit unseremX
-Objekt von oben auf, alsoa.f(g)
, dann wird in derf
-Methode jax.m()
aufgerufen (wobeix = g
gilt). Die Methodem()
gibt es aber aus statischer Sicht nicht… Warum? Der statische Typ vonx
ist innerhalb von der “allgemeinen” Klasse A leiderObject
(eben wegen Type Erasure). Wegen Zeile 2 kompiliert das Programm also nicht. Der sog. raw type ist deshalbObject
, weilT
nicht eingeschränkt ist. Wäre die Definition stattdessenA<T extends X>
, so würdeT
durchX
statt Objekt erased werden und alles würde wie erwartet laufen.
Auch bei Attributtypen werden entsprechend ersetzt. Betrachten wir wiederA<X> a = new A<X>();
, dann ist der statische Typ vona.t
hierX
und der Aufrufa.t.m()
wäre möglich. Benutzen wir das Attribut aber innerhalb von A, dann ist der Typ vont
wiederObject
… Währenda.t.m()
also funktioniert, wäre der Aufruft.m()
innerhalb von A nicht möglich (aus dem gleichen Grund weshalb auchx.m()
nicht möglich ist).
Type Erasure kann noch komplizierter werden, wenn es bspw. um Überschreibung geht (siehe Altklausur WS 18/19 Wdh.). Nähere Erklärungen dazu mache ich hier nicht, weil das zu kompliziert zu schreiben ist. Man lernt es außerdem besser, indem selbst Beispiele in der IDE macht. Type Erasure haben wir bspw. bei Aufruf 8 in meiner GenericPoly Aufgabe – schau dir das mal an + Erklärung dazu.