Falconbyte unterstüzen
Betrieb und Pflege von Falconbyte brauchen viel Zeit und Geld. Um dir auch weiterhin hochwertigen Content anbieten zu können, kannst du uns sehr gerne mit einem kleinen "Trinkgeld" unterstützen.
Thema in Kurzform
- Mit Vererbung ersparen wir uns doppelten Code.
- Die Wartung des Programms wird deutlich vereinfacht: Eine Änderung des Codes in einer Oberklasse wirkt sich automatisch auf die Unterklassen aus.
- Um eine Klasse von einer anderen erben zu lassen, verwenden wir das Schlüssekwort extends.
Wie funktioniert Vererbung?
Um den Sinn von Vererbung zu verstehen, schauen wir uns zuvor einmal folgende drei Klassen an:
class Hund {
int alter;
int groesse;
public void geraeuschMachen(){
System.out.println("Mache ein Geräusch...");
}
}
class Katze {
int alter;
int groesse;
public void geraeuschMachen(){
System.out.println("Mache ein Geräusch...");
}
}
class Eisbaer {
int alter;
int groesse;
public void geraeuschMachen(){
System.out.println("Mache ein Geräusch...");
}
}
Erkennen Sie das Problem dieser Klassen? Richtig: Derselbe Code ist mehrfach geschrieben und damit verstoßen wir gegen eine der wichtigsten Regeln guten Programmierstils: Don't repeat yourself!
Genau hier setzt die Vererbung an: Wir prüfen, welche Member (Instanzvariablen und Methoden) die unterschiedlichen Klassen gemeinsam haben und schreiben daraus eine neue übergeordnete Klasse namens Saeugetier.
Die Klasse Saeugetier ist dann die Superklasse für die Unterklassen Hund, Katze, Eisbaer. Durch die Vererbung erhalten die Unterklassen alle Member der Oberklasse, sodass wir diese nicht mehr explizit im jeweiligen Klassencode der Unterklassen schreiben müssen.
So entsteht eine Vererbungshierarchie:
Wir sehen in der Grafik, dass die drei Unterklassen von der einen Oberklasse erben. In Java sagen wir dazu, dass eine Unterklasse die Oberklasse erweitert. Die Instanzvariablen alter und groesse sowie die Methode geraeuschMachen() sind nun auch in den Unterklassen verfügbar.
extends
Um eine Vererbungsbeziehung herzustellen, muss das Schlüsselwort extends (dt.: erweitert) und der Name der Oberklasse zur Klassendefinition der Unterklasse hinzugefügt werden:
public class Katze extends Saeugetier{ }
Die Unterklasse Katze erweitert jetzt die Oberklasse Saeugetier. Das heißt konkret: Auch wenn es im Code der Klasse Katze nicht explizit geschrieben steht, so besitzt sie durch Vererbung nun auch die Instanzvariablen alter und groesse sowie die Methode geraeuschMachen(). Für die übrigen abgeleiteten Klassen funktioniert das genauso.
Methoden überschreiben
Wenn wir nun Objekte der Unterklassen Hund, Katze, Eisbaer erstellen, können wir die von der Oberklasse geerbte Methode geraeuschMachen() ganz einfach auf diesen Objekten aufrufen:
public static void main(String[] args){
new Hund().geraeuschMachen(); // Mache ein Geräusch...
new Katze().geraeuschMachen(); // Mache ein Geräusch...
new Eisbaer().geraeuschMachen(); // Mache ein Geräusch...
}
Allerdings ergibt sich jetzt ein neues Problem: In unserem Beispiel machen alle Tiere dasselbe Geräusch. Da nämlich die Unterklassen Hund, Katze und Eisbaer die Methode geraeuschMachen() so vererbt bekommen, wie sie in Saeugetier implementiert ist, ist der Code der Methode für alle Unterklassen identisch.
Das sollten wir ändern, damit unsere Tiere unterschiedliche Geräusche machen!
Dazu müssen wir die Methode geraeschMachen() in allen Unterklassen typspezifisch programmieren. Dies gelingt uns, indem wir die Methode in den Unterklassen überschreiben, damit jedes Tier sein eigenes Geräusch machen kann.
- Eine Unterklasse kann die Implementierung einer geerbten Methode überschreiben. Beim Aufruf der Methode auf einem Objekt der Unterklasse wird dann die überschriebene Methode ausgeführt
- Um eine Methode zu überschreiben, müssen wir in den Unterklassen die Methode noch einmal mit demselben Methoden-Kopf wie in der Oberklasse deklarieren, aber einen anderen Rumpf implementieren.
- Wir sollten vor den zu überschreibenden Methoden noch die Annotation @Override setzen (Vorteile: Code ist besser lesbar und der Compiler stellt sicher, dass die Methode korrekt überschrieben wird).
Für unsere Methode geraeuschMachen() sieht das dann so aus:
class Hund extends Saeugetier{
@Override
public void geraeuschMachen(){
System.out.println("Wuff!");
}
}
class Katze extends Saeugetier{
@Override
public void geraeuschMachen(){
System.out.println("Miau!");
}
}
class Eisbaer extends Saeugetier{
@Override
public void geraeuschMachen(){
System.out.println("Bruumm!");
}
}
Der Methodenkörper ist jetzt tierspezifisch ausprogrammiert. Beim Aufruf auf einem Objekt der Klassen Hund, Katze, Eisbaer werden die überschriebenen Methoden ausgeführt:
public static void main(String[] args){
new Hund().geraeuschMachen(); // Wuff!!
new Katze().geraeuschMachen(); // Miau!
new Eisbaer().geraeuschMachen(); // Brumm!
}
Somit machen unterschiedliche Tiere auch unterschiedliche Geräusche :-)
Variablen bei Vererbung
Variablen werden in Java sehr unkompliziert vererbt, wie wir im folgenden Beispiel sehen:
class Saeugetier {
int alter = 5;
}
class Katze extends Saeugetier {
}
class Playground {
public static void main(String[] args){
System.out.println(new Katze().alter); // 5
}
}
Die in der Superklasse Saeugetier definierte Instanzvariable alter wird an die Unterklasse Katze vererbt. Damit verfügen auch Katzen-Objekte über ein Alter.
Werden private Instanzvariablen vererbt?
class Saeugetier {
private int alter = 5;
}
class Katze extends Saeugetier { // Habe ich auch die Variable alter? }
Klare Antwort: Ja, private Instanzvariablen sind existent in der Unterklasse. Allerdings können wir bedingt durch den private Modifier nicht direkt auf sie zugreifen. Der Aufruf new Katze().alter; würde daher einen Compiler-Fehler verursachen.
Über einen Methodenaufruf erhalten wir aber indirekt Zugriff auf die private Instanzvariable. Hier der angepasste Code:
class Saeugetier {
private int alter = 5;
public int getAlter(){
return alter;
}
}
class Katze extends Saeugetier { }
class Playground {
public static void main(String[] args){
System.out.println(new Katze().getAlter()); // 5
}
}
Übungen
einfach
Überschreibe die Methode printInfos() der Superklasse Car in der Unterklasse Ferrari so, dass sowohl der Wert der Instanzvariablen horsePower als auch model auf der Konsole angezeigt werden:
class Car {
int horsePower;
public void printInfos(){
System.out.println(horsePower);
}
}
class Ferrari extends Car{
String model;
public Ferrari(int hp, String model){
horsePower = hp;
this.model = model;
}
}
mittel
Wieviele Compiler-Fehler sind im folgenden Code?
class Car {
public void action1(){
System.out.println("I drive fast");
}
public void action2(String c){
System.out.println("The color is " + c);
}
}
class Ferrari extends Car{
public void action1(int x){
System.out.println("Driving fast at " + x + " km/h");
}
public String action2(String c){
System.out.println("The color is " + c);
return c;
}
}
A. 1
B. 2
C. 3
D. Keiner. Der Code ist sauber.
schwer
Sieh dir folgende Vererbungshierarchie an. Welche Aussage dazu ist richtig?
A. new Testarossa().methodX(); ruft methodX() aus der Klasse Car auf.
B. new California().methodX(); verursacht einen Compiler-Fehler.
C. new California().methodX(); ruft methodX() aus der Klasse Car auf.
D. new Ferrari().methodX(); verursacht einen Compiler-Fehler.
E. new Audi().methodX(); ruft methodX() aus der Klasse Car auf.