6.7 Vererbung
 
Neben der Assoziation von Objekten gibt es in der Objektorientierung eine weitere wichtige Möglichkeit zur Wiederverwendung, die Vererbung. Sie basiert auf der Idee, dass Eltern ihren Kindern Eigenschaften mitgeben. Vererbung bindet die Klassen noch dichter aneinander. Mittels dieser engen Verbindung können wir später sehen, dass Klassen in gewisser Weise austauschbar sind.
6.7.1 Vererbung in Java
 
Die Klassen in Java sind in einer Hierarchie geordnet. Von Object erben automatisch alle Klassen, direkt oder indirekt. Eine neu definierte Klasse kann durch das Schlüsselwort extends eine Klasse erweitern. Sie wird dann zur Unter- oder Subklasse beziehungsweise Kindklasse. Die Klasse, von der die Unterklasse erbt, heißt Oberklasse (auch Superklasse oder Elternklasse). Durch den Vererbungsmechanismus werden alle sichtbaren Eigenschaften der Oberklasse auf die Unterklasse übertragen. Eine Oberklasse vererbt also Eigenschaften und die Unterklasse erbt sie.
Syntaktisch wird die Vererbung durch das Schlüsselwort extends beschrieben. Allgemein gilt für eine erbende Klasse Unter und eine Oberklasse Ober:
class Unter extends Ober
{
}
6.7.2 Einfach- und Mehrfachvererbung
 
In Java ist auf direktem Weg nur die Einfachvererbung (engl. single inheritance) erlaubt. In der Einfachvererbung kann eine Klasse lediglich eine andere erweitern. In Programmiersprachen wie C++ können auch mehrere Klassen zu einer neuen verbunden werden. Dies bringt aber einige Probleme mit sich, die in Java vermieden werden. Wie müsste etwa eine Unterklasse auf die Eigenschaften einer Oberklasse zugreifen, wenn beide Oberklassen das gleiche Attribut oder die gleiche Methode definieren? Wenn die Unterklasse etwas von der Oberklasse nutzt, welche Oberklasse soll angesprochen werden? In C++ wird das durch einen Scope-Operator (::) gelöst, sodass dem Programmierer bewusst sein muss, welche Eigenschaft von welcher Oberklasse kommt.
Dazu gesellt sich auch das Diamanten- oder Rauten-Problem. Zwei Klassen K1 und K2 erben von einer Oberklasse O eine Eigenschaft x. Eine Unterklasse U könnte von den Klassen K1 und K2 erben. Was ist mit der Eigenschaft x in O? Da sie eigentlich nur einmal vorliegt, dürfte es keinen Grund zur Sorge geben. Dennoch stellt dieses Szenario ein Problem dar, welches durch Einfachvererbung nicht entstehen kann. Da die Mehrfachvererbung in Java nicht gültig ist, steht letztendlich hinter dem Schlüsselwort extends lediglich eine einzige Klasse.
6.7.3 Kleidungsstücke modelliert
 
Wir wollen nun eine Klassenhierarchie für Kleidungsstücke aufbauen. Die Hierarchie geht von oben nach unten, von der Superklasse zur Subklasse. Die Klasse Kleidung erbt automatisch alles von Object, Socke erbt alle Eigenschaften von Kleidung. Damit die Socke nicht so alleine ist, geben wir ihr eine Hose als Partner hinzu. Damit ergibt sich das nachfolgende UML-Diagramm. Vererbung ist durch einen Pfeil in Richtung der Oberklasse angegeben:
Abbildung 6.5
Socke und Hose sind Unterklassen von Kleidung
|
Listing 6.18
SchrankVoll.java, Teil 1
class Kleidung
{
public String farbe;
}
Eine kleine Klasse Kleidung deklariert nur das Attribut farbe. Wird die Unterklasse Kleidung nun zu Socke oder Hose erweitert, so kann jedes Objekt der Unterklasse problemlos auf die Variable zugreifen. Dies haben wir in dem folgenden Beispiel jedoch nicht genutzt.
Listing 6.19
SchrankVoll.java, Teil 2
class Socke extends Kleidung
{
String kennung()
{
return "Ich bin eine Socke";
}
}
class Hose extends Kleidung
{
String kennung()
{
return "Ich bin eine Hose";
}
}
Zu guter Letzt folgt eine Probeklasse, die eine Socke und eine Hose erzeugt und dann die Farbe setzt.
Listing 6.20
SchrankVoll.java, Teil 3
public class SchrankVoll
{
public static void main( String args[] )
{
Socke s = new Socke();
s.farbe = "rot";
Hose h = new Hose();
h.farbe = "grün";
System.out.println( s.farbe );
System.out.println( h.farbe );
}
}
Wir sehen an diesem Beispiel, dass der Nutzer die Farben setzen kann, da eine Hose und eine Socke das Farbattribut erben.
6.7.4 Sichtbarkeit
 
Die Vererbung kann durch private eingeschränkt werden. Eine Subklasse erbt dann alles von einer Superklasse, was nicht private ist. Zusätzlich kommt zu private noch eine Sonderform protected hinzu. Hier kann auch eine Unterklasse alle Eigenschaften sehen. Nur von außen sind die Eigenschaften privat. Eine Ausnahme bilden jedoch Klassen, die im gleichen Paket sind.
6.7.5 Das Substitutionsprinzip
 
Stellen wir uns vor, Bekannte kommen ausgehungert von einer Wandertour und fragen: »Haste was zu essen?«. Die Frage zieht wohl darauf ab, dass es bei Hunger ziemlich egal ist, was wir anbieten, wichtig ist nur etwas Essbares. Daher können wir Eis, aber auch Frittierfett anbieten.
Diese Ausgangslage führt uns zu einem wichtigen Konzept in der Objektorientierung: Wenn wenig gefordert wird, kann mehr angeboten werden. Genauer gesagt, wenn eine Unterkasse U die Oberklasse O erweitert, können wir überall, wo O gefordert wird, etwa als Parameter einer Funktion, auch ein U übergeben, denn wir werden mit der Unterklasse nur spezieller. Derjenige, dem wir mehr übergeben, kann damit zwar nichts anfangen, aber ablehnen wird er das Objekt nicht, da es alle geforderten Eigenschaften aufweist.
Da an Stelle eines Objekts auch ein Objekt der Unterklasse auftauchen kann, sprechen wir von Substitution. Das Prinzip wurde von Professorin Barbara Liskov formuliert und nennt sich daher auch Liskov’sches Substitutionsprinzip.
Bleiben wir bei unserem Beispiel des Parameters. Für unsere Anzieh-Vererbungsbeziehung heißt das, überall dort, wo Kleidung gefordert ist, können wir eine Socke oder auch eine Hose übergeben. In der Java-Bibliothek finden sich endlose weitere Beispiele. Häufigstes Anwendungsfeld sind Datenstrukturen – etwa eine Liste. Die Datenstrukturen nehmen beliebige Objekte entgegen, denn der Parametertyp ist Object – zu sehen etwa an der Methode add(Object) in java.util.ArrayList, der Klasse für Listen. Die Substitution besagt, dass wir alle Objekte dort einsetzen können, da alle Klassen von Object abgeleitet sind.
6.7.6 Automatische und Explizite Typanpassung
 
Das folgende Beispiel zeigt, dass auch ein Exemplar einer Unterklasse einer Variablen vom Typ der Oberklasse zugewiesen werden kann. Wir erzeugen zunächst ein Socke-Objekt:
Socke omi = new Socke();
Kleidung k = omi;
Da eine Socke ein spezielles Kleidungsobjekt ist (Socke ist Unterklasse von Kleidung), funktioniert diese Zuweisung. Auf den ersten Blick erscheint das nicht sonderlich sinnvoll, erfüllt aber einen Zweck: k übernimmt alle Eigenschaften eines Kleidungsobjekts von der mächtigeren Klasse Socke, verzichtet aber auf alle anderen Informationen, die eine Socke oder sonstige Unterklasse noch bietet, beispielsweise die Methode kennung().
Die Klasse Kleidung bietet dabei das Attribut farbe an, sodass auch Folgendes problemlos ist:
System.out.println( k.farbe );
Versuchen wir aber eine spezielle Eigenschaft von Socke zu benutzen, etwa die Methode kennung(), so ist dies nicht möglich:
k.kennung(); // geht nicht
Hier ist der Typ der Variable k entscheidend. Der Compiler hat k vom Typ Kleidung kennen gelernt, daher weiß er nicht, dass k eigentlich ein verkapptes Socken-Objekt ist.
Genauso gut lässt sich keine neue Referenz vom Typ Socke auf die Kleidung legen. Hier gilt wiederum, dass die Typen unvereinbar sind, sodass wir einen Compilerfehler erhalten:
Socke opi = k; // geht nicht
Es ist aber möglich, das Objekt k durch eine Typumwandlung in eine Socke umzuwandeln. Dies funktioniert aber lediglich dann, wenn k auch wirklich eine Socke ist. Dem Compiler ist dies in dem Moment egal. Diese Bedingung wird erst zur Laufzeit geprüft:
Socke opi = (Socke) k; // geht wohl
Wir werden in den folgenden beiden Abschnitten nun kennen lernen, wieso das Sinn macht und es ein mächtiges Konzept ist. Wir werden sehen, dass eine Basisklasse geschaffen werden kann und diese verschiedenen Unterklassen Grundfunktionalität beibringen kann. So liefert die Basisklasse einen gemeinsamen Nenner.
Fassen wir die oberen Zeilen noch einmal in einem kompletten Programm, aber mit anderen Klassennamen zusammen:
Listing 6.21
WasIstAllesKleidung.java
class WKleidung
{
public String farbe;
}
class WSocke extends WKleidung
{
String kennung()
{
return "Ich bin eine Socke";
}
}
public class WasIstAllesKleidung
{
public static void main( String args[] )
{
WSocke omi = new WSocke();
omi.farbe = "rot";
WKleidung k = omi;
System.out.println( k.farbe ); // ist rot
// Socke opi = k; // geht nicht
WSocke opi = (WSocke) k; // geht wohl
}
}
6.7.7 Finale Klassen
 
Soll eine Klasse keine Unterklassen bilden, so werden Klassen mit dem Modifizierer final versehen. Dadurch kann vermieden werden, dass Unterklassen Eigenschaften nachträglich verändern können. Ein Versuch, von einer finalen Klasse zu erben, führt zu einem Compilerfehler. Dies schränkt zwar die objektorientierte Wiederverwendung ein, wird aber aufgrund von Sicherheitsaspekten in Kauf genommen. Eine Passwortüberprüfung soll zum Beispiel nicht einfach überschrieben werden können. Wenn die Klasse String nicht final wäre, wäre nicht sichergestellt, dass Strings unveränderlich bleiben.
6.7.8 Unterklassen prüfen mit dem Operator instanceof
 
In Java haben die Entwickler einen Operator in den Wortschatz aufgenommen, mit dem Exemplare auf ihre Verwandtschaft mit einer Klasse geprüft werden können. Mit dem Operator instanceof kann zur Laufzeit festgestellt werden, ob ein definiertes Objekt ein Exemplar einer Unterklasse einer anderen Klasse ist. Dies ist sinnvoll, denn durch objektorientiertes Programmieren werden laufend Basisobjekte definiert und erweitert. Ein weiterer Grund zur Einführung des Operators war auch, dass es (noch) keine generischen Typen in Java gibt und Daten, die aus einer Datenstruktur kommen, automatisch vom Typ der Basisklasse sind.
boolean b;
String str = "Toll";
b = ( str instanceof String ); // wahr
b = ( str instanceof Object ); // wahr
b = ( str instanceof Date ); // nö
Deklariert ist eine Variable str als Objekt vom Typ String. Für den zweiten Fall gilt: Alle Objekte gehen irgendwie aus Object hervor und sind somit logischerweise Erweiterungen. Im dritten Fall ist Date keine Basisklasse für String, der Ausdruck ist falsch.
|