Java Tutorial #31

Polymorphie in Java

2019-06-10 | credit: ©sashagrunge/ stock.adobe

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

Polymorphe Variablen und Subtyping

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: 

  • Eine Variable kann auf ein Objekt verweisen, dessen Typ entweder gleich dem deklarierten Typ der Referenzvariablen oder ein beliebiger Subtyp davon ist. 

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:

Java Polymorphie Beispiel Infografik

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.

Infografik Java Polymorphie Vererbung

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!

Der Vorteil der Polymorphie

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.

Polymorphie bei Methodenparametern

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 bei Sammlungen

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);

Polymorphie bei Methoden

Wenn es bei Polymorphie um Methoden geht, müssen wir noch einen Schritt weiterdenken:

Infografik Java Polymorphie bei Methoden

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: 

  • Wenn wir auf einer Referenz eine Methode aufrufen wollen, muss diese in der Klasse des Referenztyps vorhanden sein. 

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().

Das "Problem" der Polymorphie

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 🤗

Werbung

Java lernen

Werde zum Java Profi!

PHP Lernen

Lerne serverbasierte Programmierung

JavaScript lernen

Skille dein Webcoding

FALCONBYTE.NET

Handmade with 🖤️

© 2018-2023 Stefan E. Heller

Impressum | Datenschutz | Changelog

Falconbyte Youtube Falconbyte GitHub facebook programmieren lernen twitter programmieren lernen discord programmieren lernen