From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Eines der wesentlichen Designmerkmale objektorientierter Sprachen
+ist die Möglichkeit, Variablen und Methoden zu Klassen zusammenzufassen.
+Ein weiteres wichtiges Merkmal ist das der Vererbung, also
+der Möglichkeit, Eigenschaften vorhandener Klassen auf neue Klassen
+zu übertragen. Fehlt diese Fähigkeit, bezeichnet man die
+Sprache auch als lediglich objektbasiert.
+
+
+Man unterscheidet dabei zwischen einfacher Vererbung,
+bei der eine Klasse von maximal einer anderen Klasse abgeleitet werden
+kann, und Mehrfachvererbung, bei der
+eine Klasse von mehr als einer anderen Klasse abgeleitet werden kann.
+In Java gibt es lediglich Einfachvererbung, um den Problemen aus dem
+Weg zu gehen, die durch Mehrfachvererbung entstehen können. Um
+die Einschränkungen in den Designmöglichkeiten, die bei
+Einfachvererbung entstehen, zu vermeiden, wurde mit Hilfe der Interfaces
+eine neue, restriktive Art der Mehrfachvererbung eingeführt.
+Wir werden später darauf zurückkommen.
+
+
+
+
+
+Um eine neue Klasse aus einer bestehenden abzuleiten, ist im Kopf
+der Klasse mit Hilfe des Schlüsselworts extends
+ein Verweis auf die Basisklasse anzugeben. Hierdurch erbt die abgeleitete
+Klasse alle Eigenschaften der Basisklasse, d.h. alle Variablen und
+alle Methoden. Durch Hinzufügen neuer Elemente oder Überladen
+der vorhandenen kann die Funktionalität der abgeleiteten Klasse
+erweitert werden.
+
+
+Als Beispiel wollen wir eine neue Klasse Cabrio
+definieren, die sich von Auto
+nur dadurch unterscheidet, dass sie zusätzlich die Zeit, die
+zum Öffnen des Verdecks benötigt wird, speichern soll:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 8 - OOP II: Vererbung, Polymorphismus und statische Elemente
+
+
+
+
+
+8.1 Vererbung
+
+
+
+
+
+8.1.1 Ableiten einer Klasse
+
+
+
+
+Listing 8.1: Ein einfaches Beispiel für Vererbung
+
+
+
+
+
+001 class Cabrio
+002 extends Auto
+003 {
+004 int vdauer;
+005 }
+
+
+Wir können nun nicht nur auf die neue Variable vdauer, +sondern auch auf alle Elemente der Basisklasse Auto +zugreifen: + + +
+
+
+
+001 Cabrio kfz1 = new Cabrio();
+002 kfz1.name = "MX5";
+003 kfz1.erstzulassung = 1994;
+004 kfz1.leistung = 115;
+005 kfz1.vdauer = 120;
+006 System.out.println("Alter = "+kfz1.alter());
+
+ |
+
+
![]() |
+
+
+ +Die Vererbung von Klassen kann beliebig tief geschachtelt werden. +Eine abgeleitete Klasse erbt dabei jeweils die Eigenschaften der unmittelbaren +Vaterklasse, die ihrerseits die Eigenschaften ihrer unmittelbaren +Vaterklasse erbt usw. Wir können also beispielsweise die Klasse +Cabrio verwenden, um daraus +eine neue Klasse ZweisitzerCabrio +abzuleiten: |
+
+
|
+![]() |
+
+
+
+
+001 class ZweisitzerCabrio
+002 extends Cabrio
+003 {
+004 boolean notsitze;
+005 }
+
+ |
+
+Diese könnte nun verwendet werden, um ein Objekt zu instanzieren, +das die Eigenschaften der Klassen Auto, +Cabrio und ZweisitzerCabrio +hat: + + +
+
+
+
+001 ZweisitzerCabrio kfz1 = new ZweisitzerCabrio();
+002 kfz1.name = "911-T";
+003 kfz1.erstzulassung = 1982;
+004 kfz1.leistung = 94;
+005 kfz1.vdauer = 50;
+006 kfz1.notsitze = true;
+007 System.out.println("Alter = "+kfz1.alter());
+
+ |
+
+
![]() |
+
+
+ +Nicht jede Klasse darf zur Ableitung neuer Klassen verwendet werden. +Besitzt eine Klasse das Attribut final, +ist es nicht erlaubt, eine neue Klasse aus ihr abzuleiten. Die möglichen +Attribute einer Klasse werden im nächsten Abschnitt erläutert. |
+
+
|
+![]() |
+
+Enthält eine Klasse keine extends-Klausel, +so besitzt sie die implizite Vaterklasse Object. +Jede Klasse, die keine extends-Klausel +besitzt, wird direkt aus Object +abgeleitet. Jede explizit abgeleitete Klasse stammt am oberen Ende +ihrer Vererbungslinie von einer Klasse ohne explizite Vaterklasse +ab und ist damit ebenfalls aus Object +abgeleitet. Object +ist also die Superklasse aller anderen Klassen. + +
+Die Klasse Object +definiert einige elementare Methoden, die für alle Arten von +Objekten nützlich sind: +
+
+
++boolean equals(Object obj) + +protected Object clone() + +String toString() + +int hashCode() ++ + |
++java.lang.Object | +
+Die Methode equals +testet, ob zwei Objekte denselben Inhalt haben, clone +kopiert ein Objekt, toString +erzeugt eine String-Repräsentation +des Objekts, und hashCode +berechnet einen numerischen Wert, der als Schlüssel zur Speicherung +eines Objekts in einer Hashtable +verwendet werden kann. Damit diese Methoden in abgeleiteten Klassen +vernünftig funktionieren, müssen sie bei Bedarf überlagert +werden. Für equals +und clone +gilt das insbesondere, wenn das Objekt Referenzen enthält. + + + + +
+Neben den Membervariablen erbt eine abgeleitete Klasse auch die Methoden +ihrer Vaterklasse (wenn dies nicht durch spezielle Attribute verhindert +wird). Daneben dürfen auch neue Methoden definiert werden. Die +Klasse besitzt dann alle Methoden, die aus der Vaterklasse geerbt +wurden, und zusätzlich die, die sie selbst neu definiert hat. + +
+Daneben dürfen auch bereits von der Vaterklasse geerbte Methoden +neu definiert werden. In diesem Fall spricht man von Überlagerung +der Methode. Wurde eine Methode überlagert, wird beim Aufruf +der Methode auf Objekten dieses Typs immer die überlagernde Version +verwendet. + +
+Das folgende Beispiel erweitert die Klasse ZweisitzerCabrio +um die Methode alter, das nun +in Monaten ausgegeben werden soll: + + +
+
+
+
+001 class ZweisitzerCabrio
+002 extends Cabrio
+003 {
+004 boolean notsitze;
+005
+006 public int alter()
+007 {
+008 return 12 * (2000 - erstzulassung);
+009 }
+010 }
+
+ |
+
+Da die Methode alter bereits +aus der Klasse Cabrio geerbt +wurde, die sie ihrerseits von Auto +geerbt hat, handelt es sich um eine Überlagerung. Zukünftig +würde dadurch in allen Objekten vom Typ ZweisitzerCabrio +bei Aufruf von alter die überlagernde +Version, bei allen Objekten des Typs Auto +oder Cabrio aber die ursprüngliche +Version verwendet werden. Es wird immer die Variante aufgerufen, die +dem aktuellen Objekt beim Zurückverfolgen der Vererbungslinie +am nächsten liegt. + + + + +
+Nicht immer kann bereits der Compiler entscheiden, welche Variante +einer überlagerten Methode er aufrufen soll. In Abschnitt 4.6 +und Abschnitt 7.1.6 wurde +bereits erwähnt, dass das Objekt einer abgeleiteten Klasse zuweisungskompatibel +zu der Variablen einer übergeordneten Klasse ist. Wir dürfen +also beispielsweise ein Cabrio-Objekt +ohne weiteres einer Variablen vom Typ Auto +zuweisen. + +
+Die Variable vom Typ Auto kann +während ihrer Lebensdauer also Objekte verschiedenen Typs enthalten +(insbesondere solche vom Typ Auto, +Cabrio und ZweisitzerCabrio). +Damit kann natürlich nicht schon zur Compile-Zeit entschieden +werden, welche Version einer überlagerten Methode aufgerufen +werden soll. Erst während das Programm läuft, ergibt sich, +welcher Typ von Objekt zu einem bestimmten Zeitpunkt in der Variable +gespeichert wird. Der Compiler muss also Code generieren, um dies +zur Laufzeit zu entscheiden. Man bezeichnet dies auch als dynamisches +Binden. + +
+In C++ wird dieses Verhalten durch virtuelle Funktionen realisiert +und muss mit Hilfe des Schlüsselworts virtual +explizit angeordnet werden. In Java ist eine explizite Deklaration +nicht nötig, denn Methodenaufrufe werden immer dynamisch interpretiert. +Der dadurch verursachte Overhead ist allerdings nicht zu vernachlässigen +und liegt deutlich über den Kosten eines statischen Methodenaufrufs. +Um das Problem zu umgehen, gibt es mehrere Möglichkeiten, dafür +zu sorgen, dass eine Methode nicht dynamisch interpretiert wird. Dabei +wird mit Hilfe zusätzlicher Attribute dafür gesorgt, dass +die betreffende Methode nicht überlagert werden kann: +
+
![]() |
+
+
+ +In Abschnitt 8.4 werden +wir das Thema Polymorphismus noch einmal aufgreifen und ein +ausführliches Beispiel für dynamische Methodensuche geben. |
+
+
|
+![]() |
+
+Wird eine Methode x in einer +abgeleiteten Klasse überlagert, wird die ursprüngliche Methode +x verdeckt. Aufrufe von x +beziehen sich immer auf die überlagernde Variante. Oftmals ist +es allerdings nützlich, die verdeckte Superklassenmethode aufrufen +zu können, beispielsweise, wenn deren Funktionalität nur +leicht verändert werden soll. In diesem Fall kann mit Hilfe des +Ausdrucks super.x() +die Methode der Vaterklasse aufgerufen werden. Der kaskadierte Aufruf +von Superklassenmethoden (wie in super.super.x()) +ist nicht erlaubt. + + + + +
+Wenn eine Klasse instanziert wird, garantiert Java, dass ein zur Parametrisierung +des new-Operators +passender Konstruktor aufgerufen wird. Daneben garantiert der Compiler, +dass auch der Konstruktor der Vaterklasse aufgerufen wird. Dieser +Aufruf kann entweder explizit oder implizit geschehen. + +
+Falls als erste Anweisung innerhalb eines Konstruktors ein Aufruf +der Methode super +steht, wird dies als Aufruf des Superklassenkonstruktors +interpretiert. super +wird wie eine normale Methode verwendet und kann mit oder ohne Parameter +aufgerufen werden. Der Aufruf muss natürlich zu einem in der +Superklasse definierten Konstruktor passen. + +
+Falls als erste Anweisung im Konstruktor kein Aufruf von super +steht, setzt der Compiler an dieser Stelle einen impliziten Aufruf +super(); ein und ruft damit +den parameterlosen Konstruktor der Vaterklasse auf. Falls ein solcher +Konstruktor in der Vaterklasse nicht definiert wurde, gibt es einen +Compiler-Fehler. Das ist genau dann der Fall, wenn in der Superklassendeklaration +lediglich parametrisierte Konstruktoren angegeben wurden und +daher ein parameterloser default-Konstruktor nicht automatisch +erzeugt wurde. +
+
![]() |
+
+
+ +Alternativ zu diesen beiden Varianten, einen Superklassenkonstruktor +aufzurufen, ist es auch erlaubt, mit Hilfe der this-Methode +einen anderen Konstruktor der eigenen Klasse aufzurufen. Um die oben +erwähnten Zusagen einzuhalten, muss dieser allerdings selbst +direkt oder indirekt schließlich einen Superklassenkonstruktor +aufrufen. |
+
+
|
+![]() |
+
+Das Anlegen von Konstruktoren in einer Klasse ist optional. Falls +in einer Klasse überhaupt kein Konstruktor definiert wurde, erzeugt +der Compiler beim Übersetzen der Klasse automatisch einen parameterlosen +default-Konstruktor. Dieser enthält lediglich einen Aufruf +des parameterlosen Superklassenkonstruktors. + + + + +
+
![]() |
+
+
+ +Konstruktoren werden nicht vererbt. Alle Konstruktoren, die in einer +abgeleiteten Klasse benötigt werden, müssen neu definiert +werden, selbst wenn sie nur aus einem Aufruf des Superklassenkonstruktors +bestehen. |
+
+
|
+![]() |
+
+Durch diese Regel wird bei jedem Neuanlegen eines Objekts eine ganze +Kette von Konstruktoren aufgerufen. Da nach den obigen Regeln jeder +Konstruktor zuerst den Superklassenkonstruktor aufruft, wird die Initialisierung +von oben nach unten in der Vererbungshierarchie durchgeführt: +zuerst wird der Konstruktor der Klasse Object +ausgeführt, dann der der ersten Unterklasse usw., bis zuletzt +der Konstruktor der zu instanzierenden Klasse ausgeführt wird. + + + + +
+
![]() |
+
+
+ +Im Gegensatz zu den Konstruktoren werden die Destruktoren eines Ableitungszweiges +nicht automatisch verkettet. Falls eine Destruktorenverkettung erforderlich +ist, kann sie durch explizite Aufrufe des Superklassendestruktors +mit Hilfe der Anweisung super.finalize() +durchgeführt werden. |
+
+
|
+![]() |
+
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |