Polymorphie in Java: Überladen und Überschreiben
Polymorphie bedeutet Vielgestaltigkeit: Du kannst verschiedene Objekte über einen gemeinsamen Obertyp ansprechen. Beim Aufruf einer überschriebenen Methode führt Java trotzdem das Verhalten der tatsächlichen Instanz aus.
Auf dieser Seite lernst du außerdem, Überschreiben von Überladen zu unterscheiden und polymorphe Aufrufe sicher vorherzusagen.
Hake ab, was du schon kannst — und komm am Ende hierher zurück!
Die Grundidee: ein Aufruf, verschiedene Reaktionen
Stell dir die Oberklasse Animal mit der Methode sound() vor. Die Unterklassen Cat und Dog überschreiben diese Methode:
Cat.sound()gibtMiauaus.Dog.sound()gibtWuffaus.
Beide Objekte können als Animal behandelt werden. Derselbe Aufruf sound() führt dennoch zu unterschiedlichem Verhalten.
Polymorphie
Polymorphie ist die Fähigkeit, Objekte verschiedener Klassen über einen gemeinsamen Obertyp oder ein gemeinsames Interface zu verwenden. Eine erreichbare überschriebene Methode kann je nach tatsächlicher Instanz unterschiedlich ausgeführt werden.
Gegeben sind Animal tier1 = new Cat(); und Animal tier2 = new Dog();.
- Beide Variablen haben den Referenztyp
Animal. - Die Instanztypen sind
CatundDog. tier1.sound()führtCat.sound()aus und ergibtMiau.tier2.sound()führtDog.sound()aus und ergibtWuff.
Der aufrufende Code verwendet nur die gemeinsame Methode sound(). Die konkrete Reaktion hängt vom jeweiligen Objekt ab.
Der gemeinsame Obertyp macht Objekte einheitlich verwendbar; die tatsächliche Instanz liefert das konkrete Verhalten.
Überladen wird zur Compile-Zeit entschieden
Beim Überladen stehen in derselben Klasse mehrere Methoden mit demselben Namen. Ihre Parameterlisten müssen sich unterscheiden. Java erkennt daher bereits beim Übersetzen des Programms, welche Methode zum Aufruf passt.
Die Klasse Greetings bietet zwei Methoden an:
print(String text)erwartet ein Argument.print(String text, String name)erwartet zwei Argumente.
Für print("Guten Morgen") wählt der Compiler die erste Methode. Für print("Guten Morgen", "Mira") wählt er die zweite.
Der Rückgabetyp allein reicht nicht aus, um überladene Methoden voneinander zu unterscheiden. Entscheidend ist die unterschiedliche Parameterliste.
Überladen: gleicher Methodenname, unterschiedliche Parameterlisten, Auswahl zur Compile-Zeit.
Überschreiben wird zur Laufzeit wirksam
Beim Überschreiben definiert eine Unterklasse eine geerbte Methode mit passender Signatur neu. Der Methodenname und die Parameter passen dabei zur geerbten Methode. Welche Implementierung ausgeführt wird, richtet sich zur Laufzeit nach der tatsächlichen Instanz.
Die Annotation @Override kennzeichnet die beabsichtigte Überschreibung. Sie hilft dem Compiler, beispielsweise einen falsch geschriebenen Methodennamen zu erkennen.
Angenommen, Animal besitzt sound() und Cat überschreibt diese Methode. Bei Animal tier = new Cat(); gilt:
- Über den Referenztyp
Animalistsound()erreichbar. - Das erzeugte Objekt ist eine Instanz von
Cat. - Deshalb führt
tier.sound()die Implementierung ausCataus.
Das Ergebnis ist Miau, nicht das allgemeine Geräusch aus Animal.
Eine überschreibende Methode kann mit super zunächst die Oberklassenmethode aufrufen und danach eigenes Verhalten ergänzen. So muss gemeinsame Logik nicht wiederholt werden.
Referenztyp und Instanztyp beantworten zwei Fragen
Bei Konto konto = new GeschaeftsKonto(); treten zwei Typen auf:
Referenztyp
Der Referenztyp steht links in der Deklaration. Hier ist es Konto. Er legt fest, welche Methoden über die Variable direkt erreichbar sind.
Instanztyp
Der Instanztyp gehört zum tatsächlich erzeugten Objekt. Hier ist es GeschaeftsKonto. Er bestimmt bei einer erreichbaren überschriebenen Methode, welche gültige Implementierung ausgeführt wird.
Nutze bei jedem polymorphen Aufruf diese zwei Fragen:
- Ist die Methode über den Referenztyp erreichbar? Falls nein, ist der direkte Aufruf nicht zulässig.
- Welche Klasse besitzt die tatsächliche Instanz? Deren gültige Überschreibung wird ausgeführt. Überschreibt sie die Methode nicht, gilt die geerbte Implementierung.
Konto konto = new GeschaeftsKonto();
konto.auszahlen()ist erlaubt, wennauszahlen()zum sichtbaren Vertrag vonKontogehört. ÜberschreibtGeschaeftsKontodie Methode, läuft diese spezialisierte Implementierung.konto.setVerfuegungsrahmen()ist nicht direkt erlaubt, wenn nurGeschaeftsKontodiese Methode anbietet. Der ReferenztypKontokennt sie nicht.
Der Instanztyp macht also nicht automatisch jede Spezialmethode über die Variable erreichbar.
Dasselbe Prinzip gilt für Interfaces: Bei Zahlbar zahlung = new PrivatKonto(); sind über zahlung nur die im Interface sichtbaren Methoden direkt aufrufbar. Für einen solchen Aufruf gilt die Implementierung der tatsächlichen Instanz oder eine von ihr geerbte Implementierung.
Eine Klassenhierarchie austauschbar erweitern
Polymorphie ist besonders nützlich, wenn aufrufender Code nur den gemeinsamen Vertrag kennen muss. Eine Sammlung vom Typ Animal kann etwa Instanzen von Cat und Dog enthalten. Eine Schleife kann für jedes Element sound() aufrufen, ohne vor jedem Aufruf die konkrete Klasse abzufragen.
Willst du Cow ergänzen, gehst du so vor:
- Leite
CowvonAnimalab oder erfülle das gemeinsame Interface. - Implementiere beziehungsweise überschreibe
sound()passend. - Füge eine
Cow-Instanz dort ein, wo einAnimalerwartet wird. - Prüfe, ob der vorhandene Aufruf
sound()ohne Sonderfall das neue Verhalten ausführt.
Nach der Ergänzung gilt Animal tier = new Cow();. Wenn Cow.sound() die Ausgabe Muh festlegt, ergibt tier.sound() den Wert Muh. Der aufrufende Code musste dafür nicht von Animal auf Cow umgestellt werden.
Das kann Erweiterungen erleichtern und doppelten Aufrufcode vermeiden. Polymorphie ist aber kein Selbstzweck: Unnötig tiefe Hierarchien oder unklare Verträge können Programme schwerer verständlich machen.
Karteikasten
Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.
Alles auf einen Blick
- Polymorphie
- Überladen: Auswahl anhand der Parameter zur Compile-Zeit
- Überschreiben: Auswahl der Implementierung zur Laufzeit
- Referenztyp: bestimmt die Erreichbarkeit
- Instanztyp: bestimmt das konkrete Verhalten
- Oberklasse oder Interface: gemeinsamer Vertrag
- Austauschbarkeit: neue Untertypen ohne besondere Aufrufe verwenden
Abschluss-Check
Wenn du einen Aufruf untersuchst, trenne immer Erreichbarkeit und Ausführung: Der Referenztyp beantwortet die erste Frage, der Instanztyp bei überschriebenen Methoden die zweite.
Mit Google fortfahren