04.06 Polymorphie
Wie man Eigenschaften einer Klasse an eine Neue vererben kann, haben Sie gerade im vorigen Kapitel 04.05 Vererbung erfahren. Es kann aber passieren, dass das Verhalten einer Methode in der Elternklasse nicht unbedingt dem gewünschten Verhalten in der Kindklasse entspricht. Aber auch dafür gibt es ein Konzept in der objektorientierten Programmierung.
Polymorphie
In Kapitel 04.05 Vererbung haben Sie gelernt, wie Objekte einer Kindklasse Methoden der Elternklasse verwenden können, ohne das diese noch einmal explizit in der Kindklasse implementiert wurden. Dies war der Fall bei der Verwendung der Methode getName
, welche in der Klasse Person
selbst implementiert wurde, aber dennoch von den Objekten der Kinderklassen Manager
und Programmierer
verwendet werden konnte. Häufig ist es aber erwünscht, dass die Kinderklasse, als speziellere Klasse der Elternklasse, auch speziellere Dinge innerhalb einer solchen Methode durchführt. Zu diesem Zweck gibt es das Konzept der Polymorphie in der objektorientierten Programmierung.
Polymorphie kommt aus dem griechischen und bedeutet Vielgestaltigkeit. Dies soll darauf hinweisen, dass Methoden innerhalb einer Vererbungslinie, trotz gleicher Methodensignatur, ein unterschiedliches Verhalten aufweisen.
Um dies nun einmal zu verdeutlichen, erweitern wir unser Beispiel aus Kapitel 04.05 Vererbung. Die Klasse Person
bekommt eine neue Methode sageEtwas
.
public class Person { private String name = ""; public Person(String aName) { this.name = aName; } public String getName() { return this.name; } public void sageEtwas() { System.out.println("Mein Name ist " + this.getName()); } }
In der Methode sageEtwas
wird einfach ein kleiner Text mit dem Namen der Person ausgegeben. Nun wäre es ja so, dass Objekte der beiden Kindklassen Manager
und Programmierer
, diese Methode erben. Ruft ein solches Objekt dann diese Methode auf, wird ebenfalls der kleine Text mit dem Namen ausgegeben. Wir möchten aber nun, dass für Manager
und Programmierer
andere Texte erscheinen, wenn wir die Methode sageEtwas
für Objekte dieser Klassen aufrufen. Dazu müssen wir die geerbte Methode innerhalb der Kindklasse überschreiben, um ihr ein anderes Verhalten zu verleihen.
public class Manager extends Person { private int gehalt; public Manager(String aName, int aGehalt) { super(aName); this.gehalt = aGehalt; } public int getGehalt() { return this.gehalt; } public void sageEtwas() { System.out.println("Mein Name ist " + this.getName()); System.out.println("Ich bin Manager mit einem stolzen Gehalt von " + this.getGehalt()); } }
Wir implementieren einfach die bereits in der Klasse Person
geschriebene Methode sageEtwas
noch einmal innerhalb der Klasse Manager
. Dabei ist darauf zu achten, dass auch die Methodensignatur übereinstimmt. Dadurch wird signalisiert, dass diese Methode dieselbe Methode der Elternklasse überschreibt. Innerhalb von sageName
verwenden wir durch this.getName()
die geerbte Methode getName
der Elternklasse Person
. Diese haben wir nicht in Manager
überschrieben. Dadurch wird diese eins zu eins von der Klasse Person
verwendet. Zusätzlich lassen wir den Manager noch mit seinem Gehalt prahlen.
Gleiches gilt auch wieder für unsere Klasse Programmierer
.
public class Programmierer extends Person { private String lieblingsSprache = ""; public Programmierer(String aName, String aSprache) { super(aName); this.lieblingsSprache = aSprache; } public String getLieblingsSprache() { return this.lieblingsSprache; } public void sageEtwas() { System.out.println("Mein Name ist " + this.getName()); System.out.println("Ich bin Programmier und arbeite am liebsten mit " + this.getLieblingsSprache()); } }
Hier wird ebenfalls eine speziellere Methode sageEtwas
implementiert. Dies testen wir nun wieder.
public static void main(String[] args) { Person personen[] = new Person[3]; personen[0] = new Person("Horst"); personen[1] = new Programmierer("Sebastian", "Java"); personen[2] = new Manager("Wendelin", 50000000); for (int i = 0; i < personen.length; i++) { personen[i].sageEtwas(); } }
In der main
Methode legen wir ein Array personen
mit verschiedenen Objekten der Klasse Person
bzw. Kinderklassen davon an. Danach soll in einer Schleife jede dieser Personen mit personen[i].sageEtwas();
etwas sagen. Wenn Sie aufmerksam mitlesen, werden Sie nun folgendes feststellen: Alle Elemente des Arrays personen
sind doch Objekte der Klasse Person
. Der Aufruf personen[i].sageEtwas()
würde also demnach bedeuten, es wird immer Person.sageEtwas()
verwendet. Dies ist aber nicht der Fall. Abhängig von dem Typ des aktuellen Objektes, wird sich zur Laufzeit die entsprechende Methode ausgesucht. In unserem Fall implementieren alle Klasse die Methode sageEtwas
. So wird im Endeffekt, passend zum aktuellen Objekt, auch die dazugehörige Methode verwendet. Als Ausgabe erhalten wir.
Mein Name ist Horst
Mein Name ist Sebastian
Ich bin Programmier und arbeite am liebsten mit Java
Mein Name ist Wendelin
Ich bin Manager mit einem stolzen Gehalt von 50000000
In den überschriebenen Methoden verwenden wir immer die Zeile
System.out.println("Mein Name ist " + this.getName());
Dies ist ja eigentlich schon Bestandteil der Methode sageEtwas
in der Elternklasse Person
. Um uns nicht immer zu wiederholen, können wir auch Methoden der Elternklasse in den Kindklassen verwenden. Dies geht mit dem Schlüsselwort super
. Das funktioniert analog zur Verwendung der Konstruktoren der Elternklasse wie in Kapitel 04.05 Vererbung beschrieben.
Schreiben wir nun einmal die Methode sageEtwas
in den Klassen Manager
und Programmierer
um.
public void sageEtwas() { super.sageEtwas(); System.out.println("Ich bin Manager mit einem stolzen Gehalt von " + this.getGehalt()); }
bzw.
public void sageEtwas() { super.sageEtwas(); System.out.println("Ich bin Programmierer und arbeite am liebsten mit " + this.getLieblingsSprache()); }
Wir verwenden nun als erstes die Methode sageEtwas
der Elternklasse und anschließend ergänzen wir die Methode sageEtwas
der Kindklasse um den speziellen Teil. Dies hat vor allem den Vorteil, dass man nun nicht mehr an drei Stellen im Quellcode Änderungen vornehmen muss, wenn sich etwas an dem generellen Teil der Methode sageEtwas
der Elternklasse ändern sollte. Führen wir nun eine Änderung der Methode sageEtwas
in der Klasse Person
durch, so hat dies auch Auswirkungen auf die Kinderklassen, welche diese Methode verwenden bzw. erben.
Was genau ist der Sinn hinter dem @Override? Ich verstehe nämlich nicht, was es so wichtig macht, es an manchen Stellen zu setzen und an manchen nicht
Hallo Cyrill,
@Override ist eine Annotation, welche es seit Java 5 gibt. Sie zeigt dem Compiler an, dass hier eine Methode einer Vaterklasse überschrieben werden soll. Zum einen erleichtert es die Lesbarkeit vom Code, weil auf den ersten Blick ersichtlich ist, dass eine Methode eine andere überschreibt. Zum anderen kann es sein, dass Sie nur denken eine Methode zu überschreiben, in Wirklichkeit tuen sie das aber nicht (bspw. Buchstabendreher im Methodennamen, Parameter stimmen nicht mit der zu überschreibenden Methode überein, …). Ohne @Override fällt das oftmals nicht sofort auf. Verwenden Sie aber @Override bei allen Methoden die Sie überschreiben möchten, meckert der Compiler, falls die Methodensignatur nicht passt, also wenn Sie eine nicht (korrekt) überschriebene Methode mit @Override versehen.
Grüße
Stefan