Thema in Kurzform
Variablen in Java sind polymorph ("vielgestaltig"). Das bedeutet, dass eine Variable auf ein Objekt verweisen kann, dessen Typ entweder gleich dem deklarierten Typ der Referenzvariablen oder ein beliebiger Subtyp davon ist
Variablen für Objekttypen (Referenzvariablen) sind in Java polymorph. "Polymorph" heißt so viel wie "vielgestaltig". Für die objektorientierte Programmierung bedeutet dies, dass eine Variable auf verschiedene Objekttypen verweisen kann - auf welche genau, darüber gibt folgende Regel Auskunft:
Lassen Sie das erst mal sacken und genießen Sie Ihren Kaffee :-)
Um uns der polymorphen Grundeigenschaft von Referenzvariablen besser zu verstehen, sehen wir uns ein kleines Beispiel an:
Im Vererbungsbaum erkennen wir, dass die Klassen Cabrio und Limousine von der Oberklasse Auto erben. Die Klasse Autosollte in einem guten Programm als abstract definiert sein; zur besseren Veranschaulichung lassen wir sie aber ausnahmsweise konkret.
Nutzen wir nun die polymorphe Eigenschaft von Variablen und weisen den Referenzen vom Typ Auto verschiedene Objekttypen zu:
Auto a1 = new Auto();
Auto a2 = new Cabrio();
Auto a3 = new Limousine();
In Zeile 1 haben wir ein gewohntes Bild: Referenztyp und Objekttyp sind gleich. Dies gilt jedoch nicht mehr für Zeile 2 und 3: Hier sind die Objekte nämlich Subtypen von Auto.
Nochmal: Wir können Variablen nur Objekte gleichen Typs oder Objekte eines passenden Subtyps zuweisen. Deshalb ist folgende Zuweisung nicht erlaubt:
Cabrio a = new Auto(); // nein!
Wir können Referenzvariablen von Subtypen (Cabrio) also niemals Objekte von Supertypen (Auto) zuweisen. Polymorphie funktioniert also nur von oben nach unten und das ist auch gut so. Alles andere wäre Wahnsinn!
Durch Polymorphie müssen wir unseren Code nicht ändern, wenn unser Programm neue Unterklassen erhält. Polymorphie erlaubt es uns außerdem, sehr flexiblen und mächtigen Code zu schreiben. Wir sehen uns hierzu zwei Beispiele an, wie sie häufig vorkommen.
Nehmen wir einmal an, wir hätten eine Autowerkstatt (repräsentiert durch die Klasse Werkstatt). Natürlich sollen dort verschiedene Autotypen repariert werden (repräsentiert durch eine statische Methode namens reparieren()). Da in Java auch Parameter-Variablen für Objekttypen polymorph sind, können wir folgenden eleganten Code schreiben:
public class Werkstatt {
public static void reparieren(Auto a){
//
}
public static void main(String[] args){
Auto a1 = new Cabrio();
Limousine a2 = new Limousine();
Cabrio a3 = new Cabrio();
reparieren(a1);
reparieren(a2);
reparieren(a3);
}
}
Wie wir sehen, ist die Parameter-Variable der Methode reparieren() mit dem Typ Auto deklariert. Dadurch, dass der Parameter polymorph ist, können neben Auto auch Cabrio und Limousine übergeben werden.
Polymorphie können wir auch bei der Arbeit mit Listen bzw. Sammlungsklassen (wie z.B. der ArrayList) voll ausnutzen. Nehmen wir zum Beispiel an, wir führen eine Liste verschiedener Autos. Auch hier soll die Sammlung ganz unterschiedliche Autotypen aufnehmen können - und tatsächlich ist das problemlos möglich. Wir müssen die ArrayList lediglich anweisen, alle Typen von Auto anzunehmen (Typ in spitze Klammern schreiben):
Auto a1 = new Cabrio();
Limousine a2 = new Limousine();
Cabrio a3 = new Cabrio();
ArrayList<Auto> liste = new ArrayList()<>;
liste.add(a1);
liste.add(a2);
liste.add(a3);
Wenn es bei Polymorphie um Methoden geht, müssen wir noch einen Schritt weiterdenken:
Die Klasse Cabrio besitzt zwei Methoden, nämlich die von Auto geerbte Methode fahren() und die ihr eigene Methode verdeckOeffnen().
Was denken Sie passiert, wenn wir folgenden Code ausführen?
Auto a1 = new Cabrio();
a1.verdeckOeffnen(); // Fehler
Die Antwort ist, dass dieser Code einen bösen Compiler-Fehler erzeugt, nämlich: cannot resolve method 'verdeckOeffnen()'.
Die Methode verdeckOeffnen() kann also nicht ausgeführt werden. Wir sind ein wenig verwundert, dass der Methodenaufruf nicht funktioniert, da doch die Klasse Cabrio die konkrete Methode verdeckOeffnen() besitzt. Was ist der Grund?
Wenn wir Methoden aufrufen, müssen wir im Zusammenhang mit der Polymorphie folgende Regel beachten:
Der Methodenaufruf hat also deshalb nicht funktioniert, da der Referenztyp von a1 eben Auto ist - und in der Klasse Auto gibt es keine Methode verdeckOeffnen() (wäre die Variable a1 vom Referenztyp Cabrio, hätte es funktioniert).
Der tiefere Grund für dieses Verhalten liegt darin, dass Java eine streng typisierte Sprache ist. Da Objekte erst zur Laufzeit erstellt werden (also nach erfolgreichem Compilieren und Programmstart), kann Java zuvor unmöglich wissen, welche Methoden der Objekttyp hat. Es muss daher zur Laufzeit des Programms sichergestellt sein, dass Methodenaufrufe auch wirklich funktionieren. Damit dies garantiert ist, geht Java beim Referenzieren auf ein Objekt davon aus, dass der Objekttyp dem Referenztyp entspricht.
Mit anderen Worten: Wenn unsere Referenz vom Typ Auto ist, können wir zugewiesene Objekte auch nur wie ein Autobehandeln - und Auto kennt keine Methode verdeckOeffnen().
Wenn wir die Polymorphie bei Parameter-Referenzen oder Listen einsetzen, müssen wir immer bedenken, dass wir nur diejenigen Methoden ausführen können, die dem deklarierten Typ entsprechen. Das heißt:
public static void meineMethode(Auto a){
a.verdeckOeffnen(); // geht nicht!
}
public static void main(String[] args){
Auto a1 = new Cabrio();
meineMethode(a1);
}
Außerdem:
Auto a1 = new Limousine();
Auto a2 = new Cabrio();
ArrayList<Auto> liste = new ArrayList()<>;
autos.get(0).verdeckOeffnen(); // geht sowieso nicht!
autos.get(1).verdeckOeffnen(); // geht nicht!
Wenn wir mit der ArrayList-Methode get() ein Objekt der Sammlung zurückliefern, geht Java davon aus, dass es sich um ein Objekt vom Typ Auto handelt, da die ArrayList ja mit ArrayList<Auto> deklariert wurde.
Das ist doch bescheuert, oder!? Jetzt machen wir uns die Mühe und lernen, Polymorphie in Methoden und Listen einzusetzen, und was passiert? Wir können auf unseren Objekten bestimmte Methoden garnicht mehr ausführen. Was nutzt ein Cabrio, wenn wir die Methode verdeckOeffnen() nicht ausführen können?
Tatsächlich gibt es eine Technik, mit der wir das wieder hinbekommen: Die Typumwandlung (Casten). Dies aber ist ein eigenes Thema und das heben wir uns für das nächste Kapitel auf 🤗
Java Basics
[Java einrichten] [Variablen] [Primitive Datentypen] [Operatoren] [if else] [switch-case] [Arrays] [Schleifen]
Objektorientierung
[Einstieg] [Variablen ] [Konstruktor] [Methoden] [Rekursion] [Statische Member] [Initializer] [Pass-by-value] [Objektsammlungen] [Objektinteraktion] [Objekte löschen]
Klassenbibliothek
[Allgemeines] [String ] [Math] [Wrapper] [Scanner] [java.util.Arrays] [Date-Time-API]
Vererbung
[Einstieg Vererbung] [Konstruktoren bei Vererbung ] [Der protected Zugriffsmodifikator] [Abstrakte Klassen und Methoden] [Polymorphie in Java] [Typumwandlung] [Die Klasse Object] [Die toString()-Methode] [Objekte vergleichen] [Was ist ein Interface?]