Polymorphie in Java: Überladen und Überschreiben
Du lernst, wie Java Methoden auswählt und wann ein Objekt einer abgeleiteten Klasse ein Objekt seiner Basisklasse ersetzen darf.
Kurz gesagt
Beim Überladen unterscheiden sich gleichnamige Methoden, also ausführbare Aktionen, durch Anzahl oder Typen ihrer Eingaben. Beim Überschreiben gibt eine abgeleitete Klasse einer geerbten Methode eine eigene Ausführung; gemeint sind hier Methoden, die du an einem Objekt aufrufst.
Java ist eine Programmiersprache, in der Klassen Baupläne für Objekte beschreiben. Ein Objekt ist ein Exemplar einer Klasse; eine Methode beschreibt eine ausführbare Aktion. Alle Beispiele sind erfunden; ein Modell ist hier eine vereinfachte Beschreibung ausgewählter Eigenschaften. Die Java-Ausschnitte brauchen den jeweils beschriebenen Programmrahmen.
Deine Lernziele
Polymorphie bedeutet Vielgestaltigkeit. Beim Programmieren können etwa gleichnamige Methoden unterschiedliche Ausführungen haben. Dafür musst du Überladen und Überschreiben unterscheiden.
Ein Parameter ist ein benannter Platzhalter für eine Eingabe. Sein Typ legt fest, welche Art von Wert übergeben werden darf. In unseren Java-Beispielen steht int für ganze Zahlen und String für eine Zeichenkette, also eine Folge von Zeichen. Der Rückgabetyp beschreibt das Ergebnis einer Methode. void bedeutet, dass die Methode keinen Wert zurückgibt. Ein Methodenkopf nennt unter anderem Rückgabetyp, Methodennamen und Parameter. Der Methodenkörper ist der Teil zwischen den geschweiften Klammern { und }; dort stehen die Anweisungen. Bei einem Methodenaufruf stehen die übergebenen Werte in runden Klammern hinter dem Methodennamen.
Beim Überladen gibt es gleichnamige Methoden mit unterschiedlichen Parameterlisten. Entscheidend sind Anzahl und Typen der Parameter, einschließlich ihrer Reihenfolge. Bei den hier verwendeten einfachen Java-Methoden bilden der Methodenname und die geordnete Liste der Parametertypen die Methodensignatur, kurz Signatur. Andere Parameternamen reichen nicht. Auch ein anderer Rückgabetyp allein erlaubt keine zusätzliche Methode mit derselben Signatur.
Eine abgeleitete Klasse übernimmt Eigenschaften und Methoden einer Basisklasse. Das heißt Vererbung; Basisklasse und Oberklasse meinen hier dasselbe. Eine abgeleitete Klasse heißt auch Unterklasse. Eine Instanzmethode wird an einem einzelnen Objekt aufgerufen. Beim Überschreiben gibt die abgeleitete Klasse einer geerbten Instanzmethode eine eigene Ausführung mit gleichem Namen und gleichen Parametertypen. In unseren Beispielen stimmen außerdem Rückgabetyp und Sichtbarkeit überein. Die Sichtbarkeit legt fest, von wo eine Methode aufgerufen werden darf: public bedeutet öffentlich zugänglich.
Methodensignatur
Bei den hier verwendeten einfachen Java-Methoden besteht die Signatur aus dem Methodennamen und der geordneten Liste der Parametertypen. Parameternamen und Rückgabetyp gehören nicht dazu.
Beispiel: Zwei Varianten eines Markierers
- 1In der Klasse Markierer stehen die Methodenköpfe public void markiere(int platz) und public void markiere(String wort). Die Methodenkörper sind hier weggelassen.
- 2Die Methoden sind überladen: markiere(6) passt zur Variante mit int, markiere("frei") zur Variante mit String.
- 3FarbMarkierer ist von Markierer abgeleitet. Setzt FarbMarkierer die geerbte Methode public void markiere(int platz) selbst um, ist das Überschreiben.
- 4Würde man in Markierer zusätzlich int markiere(int platz) deklarieren, wäre das keine gültige Überladung: Der Rückgabetyp allein unterscheidet die Signaturen nicht.
Merke: Überladen unterscheidet Parameterlisten. Überschreiben verändert die Ausführung einer geerbten Instanzmethode. Mehr Überladungen machen ein Programm nicht von selbst verständlicher.
Teste dich
Eine Variable ist ein benannter Speicherplatz. Eine Variable für Objekte speichert eine Referenz, also einen Verweis auf ein Objekt. Sie enthält nicht das Objekt selbst. Bei der Deklaration legst du den Namen und den Typ einer Variablen fest. Dieser angegebene Typ heißt Referenztyp. Die Klasse des tatsächlich erzeugten Objekts heißt Instanztyp. Eine Instanz ist ein Objekt einer Klasse.
Eine Variable vom Typ einer Basisklasse kann auf ein Objekt dieser Basisklasse oder einer davon abgeleiteten Klasse verweisen. Durch die Zuweisung wird aus dem abgeleiteten Objekt kein Objekt der Basisklasse. Referenztyp und Instanztyp dürfen deshalb verschieden sein.
In Java erzeugt new Klassenname() ein neues Objekt, sofern die beschriebene Klasse so erzeugt werden kann. Das Zeichen = weist der links stehenden Variablen den rechts stehenden Wert zu. Ein Semikolon beendet die Anweisung. Bei einer späteren Zuweisung kann dieselbe Variable auf ein anderes Objekt verweisen. Ihr Referenztyp bleibt dabei gleich.
Referenztyp und Instanztyp
Der Referenztyp gehört zur deklarierten Variablen. Der Instanztyp gehört zum tatsächlichen Objekt, auf das die Variable gerade verweist.
Beispiel: Eine Variable für zwei Leuchten
- 1Gegeben sind die Basisklasse Leuchte und ihre abgeleiteten Klassen Tischleuchte und Wandleuchte. Beide abgeleiteten Klassen lassen sich ohne Eingaben mit new erzeugen.
- 2Leuchte licht = new Tischleuchte();
- 3Danach hat licht den Referenztyp Leuchte und verweist auf ein Objekt mit dem Instanztyp Tischleuchte.
- 4licht = new Wandleuchte();
- 5Nun verweist licht auf ein Objekt mit dem Instanztyp Wandleuchte. Der Referenztyp bleibt Leuchte.
Teste dich
Der Compiler ist das Programm, das Java-Code vor der Ausführung prüft und übersetzt. Er prüft anhand des Referenztyps, ob ein Methodenaufruf erlaubt ist. Eine in der Basisklasse vorhandene Methode kann auch an einem Objekt einer abgeleiteten Klasse aufgerufen werden. Bei überladenen Methoden wird die passende Signatur vor der Ausführung bestimmt.
Für eine überschriebene Instanzmethode wird die Ausführung dagegen zur Laufzeit ausgewählt, also während das Programm läuft. Java richtet sich dabei nach dem tatsächlichen Objekt und seinem Instanztyp. Diese Auswahl heißt dynamische Bindung. Überschreibt die Instanzklasse die Methode nicht selbst, wird die passende geerbte Ausführung verwendet.
Beim Überschreiben startet die Ausführung der Basisklassenmethode nicht von selbst zusätzlich. Im folgenden Beispiel läuft beim Aufruf deshalb der Methodenkörper von Pausenansage. System.out.println(...) gibt den eingeklammerten Wert als Textzeile aus.
„Zur Laufzeit ausgewählt“ beschreibt den Auswahlmechanismus. Bei einem einfachen Beispiel kannst du das Ergebnis trotzdem schon beim Lesen vorhersagen. Die Aussagen hier beziehen sich auf Instanzmethoden.
Beispiel: Eine besondere Ansage ausgeben
- 1Die folgenden Klassen stehen außerhalb einer Methode. Die Zuweisung und der Aufruf am Ende stehen in einer zusätzlichen Testmethode, die ausgeführt wird. class beginnt die Beschreibung einer Klasse im Java-Code. extends kennzeichnet die Ableitung von einer Basisklasse.
- 2class Ansage {
- 3 public void sprich() { System.out.println("Hinweis"); }
- 4}
- 5class Pausenansage extends Ansage {
- 6 public void sprich() { System.out.println("Pause"); }
- 7}
- 8Ansage a = new Pausenansage();
- 9a.sprich();
- 10Der Punkt verbindet die Referenz mit dem Methodenaufruf. Die leeren runden Klammern bedeuten: Es werden keine Eingaben übergeben.
- 11Der Referenztyp ist Ansage, der Instanztyp Pausenansage. Die erwartete Ausgabe ist genau eine Zeile: Pause.
Merke: Der Referenztyp bestimmt, welche Methodenaufrufe möglich sind. Für eine überschriebene Instanzmethode bestimmt das tatsächliche Objekt die ausgeführte Variante.
Teste dich
Das Liskovsche Substitutionsprinzip heißt auch Ersetzbarkeitsprinzip. Es verlangt: Ein Programm, das sich auf die zugesicherten Eigenschaften einer Basisklasse verlässt, muss auch mit Objekten der abgeleiteten Klasse korrekt funktionieren, ohne dafür verändert zu werden. Dass Java eine Vererbung und Zuweisung erlaubt, beweist diese Ersetzbarkeit noch nicht.
Eine Vorbedingung legt fest, was vor einem Methodenaufruf gelten muss. Eine Nachbedingung legt fest, was die Methode nach dem Aufruf garantiert. Eine abgeleitete Klasse darf Vorbedingungen unverändert lassen oder abschwächen. Sie darf also zusätzliche Eingaben erlauben, aber bisher erlaubte Eingaben nicht ausschließen. Nachbedingungen darf sie unverändert lassen oder verstärken. Sie darf zusätzliche Ergebnisseigenschaften garantieren, aber bisher zugesicherte Eigenschaften nicht aufgeben. Auch dauerhaft zugesicherte Eigenschaften der Basisklasse müssen erhalten bleiben.
Prüfe deshalb zuerst die Zusagen der Basisklasse. Vergleiche dann die erlaubten Eingaben und die garantierten Ergebnisse der abgeleiteten Klasse damit. Eine Bezeichnung wie „ist eine besondere Art von“ reicht als Begründung nicht. Entscheidend sind die angebotenen Aktionen und ihre Zusagen. Ein einzelner erfolgreicher Test beweist nicht, dass sie für sämtliche erlaubten Fälle eingehalten werden.
Zusagen einer Klasse
Zusagen beschreiben, welche Eingaben erlaubt sind und welches Verhalten garantiert wird. Sie bilden den Maßstab für die Ersetzbarkeit. Ohne solche Angaben lässt sich das Verhalten einer Unterklasse nicht vollständig beurteilen.
Beispiel: Die Zusagen einer Ablage vergleichen
- 1Dies ist ein vereinfachtes Verhaltensmodell, kein ausführbarer Java-Code.
- 2Die Basisklasse Ablage nimmt jede Zeichenkette mit höchstens 20 Zeichen an und gibt den gespeicherten Text später unverändert zurück.
- 3Kurzablage nimmt nur Zeichenketten mit höchstens 10 Zeichen an. Damit schließt sie bisher erlaubte Eingaben aus und verschärft die Vorbedingung. Das verletzt die Ersetzbarkeit.
- 4ErweiterteAblage nimmt jede Zeichenkette mit höchstens 40 Zeichen an und gibt sie ebenfalls unverändert zurück. Sie schwächt die Vorbedingung ab und erhält die Nachbedingung.
- 5Gemessen an diesen beschriebenen Zusagen kann ErweiterteAblage die Ablage ersetzen. Für zusätzliche, hier nicht beschriebene Zusagen wäre eine weitere Prüfung nötig.
Merke: Vorbedingungen dürfen gleich bleiben oder schwächer werden. Nachbedingungen dürfen gleich bleiben oder stärker werden. Entscheidend sind die konkreten Zusagen, nicht bloß eine technisch erlaubte Vererbung.
Teste dich
Alles auf einen Blick
Polymorphie
Gleichnamige Methoden unterscheiden sich durch ihre Parameterlisten, nicht allein durch den Rückgabetyp.
Eine abgeleitete Klasse gibt einer geerbten Instanzmethode eine eigene Ausführung.
Der deklarierte Referenztyp und der Instanztyp des tatsächlichen Objekts können verschieden sein.
Bei überschriebenen Instanzmethoden wird die Ausführung zur Laufzeit anhand des Objekts ausgewählt.
Unterklassen müssen die Zusagen der Basisklasse erhalten; Vorbedingungen dürfen schwächer, Nachbedingungen stärker werden.
Die Ausgabe eines Wegweisers bestimmen
- 1Gegeben sind die Basisklasse Wegweiser und die abgeleitete Klasse Umleitung. Wegweiser besitzt die öffentliche Instanzmethode zeige() mit der Ausgabe „geradeaus“. Umleitung überschreibt sie mit der Ausgabe „links“.
- 2Die Java-Ausschnitte Wegweiser weg = new Umleitung(); und weg.zeige(); werden nacheinander in einer Methode ausgeführt. Gesucht ist die Ausgabe.
- 3Der Referenztyp ist Wegweiser. Weil diese Klasse zeige() besitzt, ist der Methodenaufruf erlaubt.
- 4new Umleitung() erzeugt ein Objekt mit dem Instanztyp Umleitung. Die Zuweisung verändert diesen Instanztyp nicht.
- 5Durch dynamische Bindung wird die überschriebene Methode von Umleitung ausgeführt. Die Ausgabe lautet „links“.
Fehler finden
Fehler in Zeile 4Die letzte Zeile wählt die Ausgabe anhand des Referenztyps statt anhand des tatsächlichen Objekts. Richtig: n.sende(); führt die überschriebene Methode von Eilmeldung aus und gibt „Eilig“ aus.
Lückentext
In derselben Klasse stehen die Methodenköpfe void sende(int zahl) und void sende(String text). Diese Methoden sind . Im Modell erbt Tanne von Baum. Nach Baum b = new Tanne(); ist der Instanztyp . Eine Basisklasse erlaubt eine Abfrage ohne Anmeldung. Verlangt die Unterklasse dafür eine Anmeldung, wird die Vorbedingung .