Informatik

Polymorphie in Java: Überladen und Überschreiben

Polymorphie in Java: Überladen und Überschreiben
Polymorphie in Java: Überladen und Überschreiben
Für Quiz, Lückentext, Lernkarten und Fortschritt ist JavaScript nötig. Alle Inhalte und Lösungen bleiben direkt lesbar.

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.

Deine Lernziele

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() gibt Miau aus.
  • Dog.sound() gibt Wuff aus.

Beide Objekte können als Animal behandelt werden. Derselbe Aufruf sound() führt dennoch zu unterschiedlichem Verhalten.

Definition

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.

Beispiel

Gegeben sind Animal tier1 = new Cat(); und Animal tier2 = new Dog();.

  1. Beide Variablen haben den Referenztyp Animal.
  2. Die Instanztypen sind Cat und Dog.
  3. tier1.sound() führt Cat.sound() aus und ergibt Miau.
  4. tier2.sound() führt Dog.sound() aus und ergibt Wuff.

Der aufrufende Code verwendet nur die gemeinsame Methode sound(). Die konkrete Reaktion hängt vom jeweiligen Objekt ab.

Merke

Der gemeinsame Obertyp macht Objekte einheitlich verwendbar; die tatsächliche Instanz liefert das konkrete Verhalten.

Teste dich
Frage 1 von 1LeichtWas zeigt dynamische Polymorphie am besten?
Lösung: Derselbe erreichbare Methodenaufruf führt bei verschiedenen Instanzen unterschiedliche Implementierungen aus. — Dynamische Polymorphie verbindet eine gemeinsame Methodenschnittstelle mit dem Verhalten der tatsächlichen Instanz.
Ü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.

Beispiel

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.

Merke

Überladen: gleicher Methodenname, unterschiedliche Parameterlisten, Auswahl zur Compile-Zeit.

Teste dich
Frage 1 von 1MittelWelche Methode wird für print("Hallo", "Mira") gewählt?
Lösung: Die Variante mit zwei Parametern. — Der Aufruf enthält zwei Argumente. Deshalb kann der Compiler die passende Methode mit zwei Parametern eindeutig auswählen.
Ü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.

Beispiel

Angenommen, Animal besitzt sound() und Cat überschreibt diese Methode. Bei Animal tier = new Cat(); gilt:

  1. Über den Referenztyp Animal ist sound() erreichbar.
  2. Das erzeugte Objekt ist eine Instanz von Cat.
  3. Deshalb führt tier.sound() die Implementierung aus Cat aus.

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.

Teste dich
Frage 1 von 1MittelWas bewirkt tier.sound() bei Animal tier = new Cat();?
Lösung: Java führt die überschreibende Methode aus Cat aus. — Cat ist ein Animal. Der Aufruf ist daher erlaubt, und die dynamische Methodenbindung wählt Cat.sound().
Referenztyp und Instanztyp beantworten zwei Fragen

Bei Konto konto = new GeschaeftsKonto(); treten zwei Typen auf:

Definition

Referenztyp

Der Referenztyp steht links in der Deklaration. Hier ist es Konto. Er legt fest, welche Methoden über die Variable direkt erreichbar sind.

Definition

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:

  1. Ist die Methode über den Referenztyp erreichbar? Falls nein, ist der direkte Aufruf nicht zulässig.
  2. Welche Klasse besitzt die tatsächliche Instanz? Deren gültige Überschreibung wird ausgeführt. Überschreibt sie die Methode nicht, gilt die geerbte Implementierung.
Beispiel

Konto konto = new GeschaeftsKonto();

  • konto.auszahlen() ist erlaubt, wenn auszahlen() zum sichtbaren Vertrag von Konto gehört. Überschreibt GeschaeftsKonto die Methode, läuft diese spezialisierte Implementierung.
  • konto.setVerfuegungsrahmen() ist nicht direkt erlaubt, wenn nur GeschaeftsKonto diese Methode anbietet. Der Referenztyp Konto kennt 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.

Teste dich
Frage 1 von 1MittelsetVerfuegungsrahmen() existiert nur in GeschaeftsKonto. Was gilt für Konto konto = new GeschaeftsKonto();?
Lösung: Die Methode ist über konto nicht direkt aufrufbar. — Referenztyp und Instanztyp haben verschiedene Aufgaben: Konto bestimmt hier die Erreichbarkeit, GeschaeftsKonto nur die Implementierung einer erreichbaren überschriebenen Methode.
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:

  1. Leite Cow von Animal ab oder erfülle das gemeinsame Interface.
  2. Implementiere beziehungsweise überschreibe sound() passend.
  3. Füge eine Cow-Instanz dort ein, wo ein Animal erwartet wird.
  4. Prüfe, ob der vorhandene Aufruf sound() ohne Sonderfall das neue Verhalten ausführt.
Beispiel

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.

Teste dich
Frage 1 von 1SchwerWoran erkennst du, dass Cow im Beispiel austauschbar eingebunden wurde?
Lösung: Vorhandener Code kann Cow als Animal verwenden und erhält über sound() das passende Verhalten. — Eine gelungene Erweiterung erfüllt den bestehenden Vertrag und liefert das klassenspezifische Verhalten über denselben Aufruf.
Karteikasten
Karteikasten

Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.

Alles auf einen Blick
Mindmap
  • 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
Teste dich
Frage 1 von 3LeichtWelche Zuordnung ist richtig?
Lösung: Überladen: Compile-Zeit; Überschreiben: Laufzeit. — Unterschiedliche Parameterlisten ermöglichen Überladen. Die dynamische Bindung einer überschriebenen Methode richtet sich nach der Instanz.
Frage 2 von 3MittelGegeben ist Animal tier = new Dog();. Dog überschreibt sound(). Was musst du für den Aufruf zuerst prüfen?
Lösung: Ob sound() über den Referenztyp Animal erreichbar ist. — Erst wird die Erreichbarkeit anhand von Animal geprüft. Anschließend bestimmt die Instanz Dog, dass Dog.sound() ausgeführt wird.
Frage 3 von 3SchwerEine neue Klasse Bird überschreibt sound(). Was geschieht bei Animal tier = new Bird(); tier.sound();?
Lösung: Die Implementierung von Bird.sound() wird ausgeführt. — Eine Unterklasseninstanz darf als Obertyp auftreten. Ist die Methode erreichbar und überschrieben, wählt Java zur Laufzeit die Implementierung des Instanztyps.

Wenn du einen Aufruf untersuchst, trenne immer Erreichbarkeit und Ausführung: Der Referenztyp beantwortet die erste Frage, der Instanztyp bei überschriebenen Methoden die zweite.

Passend dazu