04.05 Vererbung
instanceof und Cast
Wir ändern nun das vorherige Beispiel etwas ab. Wir belassen alle drei Klassen Person
, Manager
und Programmierer
so wie sie sind und ändern nur die main
Methode.
public static void main(String[] args) { Person personen[] = new Person[3]; String s = ""; 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++) { s = personen[i].getName(); if (personen[i] instanceof Manager) { s += " " + ((Manager)personen[i]).getGehalt(); } else if (personen[i] instanceof Programmierer) { s += " " + ((Programmierer)personen[i]).getLieblingsSprache(); } System.out.println(s); } }
An Stelle der drei einzelnen Objekte möchten wir nun alle Objekte in ein Feld verpacken. Dazu deklarieren wir ein Array personen
, welches Elemente von Typ Person
aufnehmen kann.
Das erste Element ist wieder unser „Horst“. Als zweites Element soll nun ein Objekt der Klasse Programmierer
eingefügt werden. Das Array kann doch aber nur Elemente des Typs Person
enthalten, werden Sie sich jetzt sicher fragen. Hierzu soll noch einmal folgendes wiederholt werden. Da die Klasse Programmierer
eine Kindklasse von Person
ist, gilt wie bereits erwähnt: Jeder Programmierer
ist eine Person
. Somit ist es auch kein Problem ein Objekt der Klasse Programmierer
in dieses Array einzufügen. Gleiches gilt auch wieder für unseren Manager „Wendelin“. Nun möchten wir aber wieder so eine schöne Ausgabe haben wie in unserem ersten Beispiel zur Vererbung. Das gestaltet sich nun nicht mehr ganz so einfach wie zuvor. Wir haben jetzt ein Feld, dessen Elemente alle Objekte der Klasse Person
sind. Wenn wir jetzt schreiben würden personen[i].getGehalt()
würde dies nicht funktionieren. Die Klasse Person
kennt nämlich keine Methode namens getGehalt
. Diese ist ja nur in unserer Klasse Manager
implementiert. Es muss also nun eine Möglichkeit geben, die im Array enthaltenen Objekte dahin gehend zu überprüfen, zu welcher Klasse sie denn gehören. Für diesen Zweck gibt es in Java den instanceof
Operator.
if (meinObjekt instanceof MeineKlasse) {}
Hiermit kann man überprüfen, ob meinObjekt
eine Instanz von MeineKlasse
ist. In unserem Fall überprüfen wir also die Elemente des Arrays dahingehend, ob es Instanzen der Klasse Manager
bzw. Programmierer
sind. Damit haben wir aber erst einmal nur die Hälfte geschafft.
Wir wissen zwar nun, dass beispielsweise unser aktuelles Feldelement ein Objekt der Klasse Manager
ist, aber dennoch ist jedes Element des Arrays immer noch vom Typ Person
. Es muss nun eine Möglichkeit geben ein Objekt, von dem wir nun wissen von welcher Klasse es ist, auch wieder in Objekt dieser Klasse umzuwandeln. Dies ist in Java recht einfach möglich.
(KonkreteKlasse)objekt
Man schreibt einfach vor das Objekt in runden Klammern die Klasse, in welche wir das dahinter stehende Objekt gerne umwandeln möchten. Man spricht hierbei von einem Cast (eng. Abguss). In unserem Fall bedeutet dies, Objekte der Klasse Person
müssen in Objekte der Klassen Manager
bzw. Programmierer
umgewandelt werden. Dies geschieht durch ((Manager)personen[i])
bzw.((Programmierer)personen[i])
. Innerhalb der Klammern wandeln wir nun ein Objekt vom Typ Person
in das entsprechende konkretere Objekt um. Somit können wir dann auch die jeweiligen Methoden der konkreten Klassen, wie getGehalt
und getLieblingsSprache
, verwenden. Als Ausgabe erhalten wir.
Horst
Sebastian Java
Wendelin 50000000
Vererbung und static
Interessant ist noch das Verhalten statischer Elemente bei der Vererbung. Hier kann es häufig zu Fehlern kommen, da man mitunter die Wirkungsweise bzw. die interne Abarbeitung nicht berücksichtigt. Hierzu ein kleines Beispiel.
public class Vater { protected static int classAttr = 0; protected int objectAttr = 0; public static void setClassAttr(int aNewValue) { Vater.classAttr = aNewValue; } public static int getClassAttr() { return Vater.classAttr; } public void setObjectAttr(int aNewValue) { this.objectAttr = aNewValue; } public int getObjectAttr() { return this.objectAttr; } }
Dies ist unsere Klasse von der später eine andere Klasse erben soll. Sie enthält jeweils ein Objekt- und ein Klassenattribut, welche mit den zugehörigen Set- bzw. Get-Methoden gesetzt bzw. abgefragt werden können. Nun lassen wir eine Klasse Kind
von der Klasse Vater
erben.
public class Kind extends Vater { public static void setClassAttr(int aNewValue) { Kind.classAttr = aNewValue; } public static int getClassAttr() { return Kind.classAttr; } public void setObjectAttr(int aNewValue) { this.objectAttr = aNewValue; } public int getObjectAttr() { return this.objectAttr; } }
Nun testen wir das Verhalten beider Klassen.
public static void main(String[] args) { Vater.setClassAttr(5); Kind.setClassAttr(3); Vater vater = new Vater(); vater.setObjectAttr(6); Kind kind = new Kind(); kind.setObjectAttr(4); System.out.println(Vater.getClassAttr()); System.out.println(Kind.getClassAttr()); System.out.println(vater.getObjectAttr()); System.out.println(kind.getObjectAttr()); }
Wir erhalten überraschend folgende Ausgabe:
3
3
6
4
Für die Ausgabe der Klassenattribute erhalten wir für Kind
und Vater
jeweils 3
und für die Objektattribute 4
und 6
. Dies hat folgenden Hintergrund. Das Klassenattribut classAttr
existiert unabhängig von einer konkreten Instanz der Klasse Vater
bzw. eines Erben davon. Es ist somit gesehen auch nur einmal vorhanden. Innerhalb der Methode public static void setClassAttr(int aNewValue)
der Klasse Kind
steht zwar Kind.classAttr = aNewValue;
dennoch ändert diese Zuweisung das Klassenattribut der Vaterklasse. Somit wird das Attribut classAttr
der Klasse Vater
innerhalb der Main-Methode erst auf 5 (Vater.setClassAttr(5);
) und danach sofort auf 3 (Kind.setClassAttr(3);
) gesetzt. Anders verhält es sich bei dem Objektattribut objectAttr
. Dieses Attribut ist immer an eine konkrete Instanz gebunden und existiert somit auch für jede einzelne Instanz. Die Methoden public void setObjectAttr(int aNewValue)
und public int getObjectAttr()
der Klasse Vater
bzw. Kind
arbeiten demnach auch immer mit dem zur Instanz zugehörigen Attribut. Als Ausgabe erhalten wir deswegen auch richtig 6 und 4.
Man kann noch hinzufügen, dass man in der Klasse Kind
auch alle Methoden weglassen könnte. Die statischen Methoden arbeiten mit dem Klassenattribut classAttr
der Vaterklasse und die Objektmethoden überschreiben auch nur die Methoden der Vaterklasse bei Verwendung der gleichen Logik. Somit würde ein
public class Kind extends Vater {}
ebenfalls zum gleichen Ergebnis führen.
Im Kapitel 04.06. Polymorphie erfahren Sie mehr dazu, wie man vererbte Methoden überschreiben kann, um ein vielgestaltiges Verhalten zu realisieren.
Momentan arbeite ich intensiv mit dem src-Code von Minecraft. Näheres Wissen ist nicht erforderlich, jedoch habe ich einige Fragen, die man auch mit normalen Java-Kenntnissen beantworten können sollte, und zwar:
1. Wie ist es möglich, von einer Kindklasse explizit einen Konstruktor zu verlangen, der den „super-Konstruktor“ aufrufen muss?
2. Wie ist es möglich, einzustellen, dass vor dem Objektnamen nur die Superklasse angegeben werden darf? Wenn man nämlich die Kindklasse angibt, wird explizit nach einem cast verlangt.
Ich hoffe, das ganze ist auch ohne Beispiele verständlich.
Hallo Cyrill Brunner,
die Kind-Klasse muss immer einen Konstruktor aufrufen. Im Zweifelsfall den Default-Konstruktor ohne Parameter (sofern vorhanden). Dieser wird auch dann ausgeführt, wenn nichts anderes angegeben ist. Vorschreiben, dass explizit ein Konstruktor aufgerufen werden muss, kann man nicht. Es sei denn man bietet nur diesen einen Konstruktor in der Vaterklasse an.
Die 2. Frage verstehe ich leider nicht, sorry! Was bedeutet „vor dem Objektnamen“? Wo soll etwas „eingestellt“ werden?
Grüße
Stefan
Das mit dem ersten habe ich jetzt verstanden.
Zum 2.:
Mit dem Objektnamen meine ich den Namen der Instanz, vor der ja immer entweder die Klasse oder der primitive Dateityp angegeben wird.
Zum Beispiel funktioniert
public static final Block Flower1 = new BlockFlower(100, 4).set…
, dies jedoch nicht:
public static final BlockFlower Flower2 = new BlockFlower(101, 5).set…
, da wird nach dem = explizit der cast (BlockFlower) verlangt. Hinzuzufügen ist, dass BlockFlower eine Kindklasse von Block ist.
Ich hoffe, ich konnte das ganze verständlich formulieren.
Grüsse
Cyrill
Ja, ich denke das Problem ist jetzt klar. Ich nehme an
new BlockFlower(100, 4).set...
erzeugt eine Instanz von
BlockFlower
, hat aber als RückgabewertBlock
?!Da BlockFlower eine Kindklasse von Block ist, kommt man hier nicht um einen Cast herum. Würde die set-Methode von BlockFlower hingegen als Return-Typ BlockFlower haben, könnte man problemlos beide Varianten (Block Flower1 und BlockFlower Flower2) verwenden. Warum? Eigentlich ist es ganz einfach, ich versuche es an einem anschaulichen Beispiel zu erklären:
Angenommen es gibt die Klassen Mensch (mit den Attributen Name und Geburtstag) und Angestellter (mit dem zusätzlichen Attribut Gehalt). Angestellter erbt natürlich von Mensch (Angestellter ist ein Mensch), so wie BlockFlower von Block erbt (BlockFlower ist ein Block). Wenn man jetzt eine Methode
erzeugeMensch
hat, die einen Menschen zurückliefert, dann kann man ja nicht einfach schreibenAngestellter a = Mensch.erzeugeMensch();
Denn ein Mensch ist kein Angestellter und hat somit auch kein Gehalt. Das Gehalt wäre also undefiniert. Umgekehrt würde natürlich
Angestellter a = Angestellter.erzeugeAngestellten();
Mensch m = Angestellter.erzeugeAngestellten();
funktionieren, da ein Angestellter ein Angestellter ist (Fall 1, problemlos möglich) und ein Angestellter eben auch ein Mensch ist (Fall 2). Denn ein Angestellter hat alle Attribute (und noch mehr), die auch ein Mensch hat. Der Mensch wäre also wohl definiert.
Würde jetzt die Methode
erzeugeMensch()
intern einen Angestellten erzeugen, also bspw. sowas:dann könnte man den Cast von Mensch nach Angestellter durchführen. Java kann aber nicht garantieren, dass es sich um einen Angestellten handelt, weil der Return-Typ eben als Mensch deklariert wurde. Das macht den Cast notwendig.
Ich hoffe der Sachverhalt ist jetzt klarer?!
Grüße
Stefan
Das mit dem Rückgabewert von Block ist das Problem, denn in der Klasse sind alle Setter mit dem Rückgabewert des Objektes bezeichnet, um das Method-chaining zu ermöglichen. Danke für die ausführliche Antwort.
Gruss
Cyrill