Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 6 Eigene Klassen schreiben
  gp 6.1 Eigene Klassen definieren
  gp 6.2 Assoziationen zwischen Objekten
  gp 6.3 Privatsphäre und Sichtbarkeit
    gp 6.3.1 Wieso nicht freie Methoden und Variablen für alle?
    gp 6.3.2 Privat ist nicht ganz privat. Es kommt darauf an, wer’s sieht
    gp 6.3.3 Zugriffsmethoden für Attribute definieren
    gp 6.3.4 Zusammenfassung zur Sichtbarkeit
  gp 6.4 Statische Methoden und Variablen
    gp 6.4.1 Warum statische Eigenschaften sinnvoll sind
    gp 6.4.2 Statische Eigenschaften mit static
    gp 6.4.3 Statische Eigenschaften als Objekteigenschaften nutzen
    gp 6.4.4 Statische Eigenschaften und Objekteigenschaften
    gp 6.4.5 Statische Variablen zum Datenaustausch
    gp 6.4.6 Warum die Groß- und Kleinschreibung wichtig ist
    gp 6.4.7 Konstanten mit dem Schlüsselwort final bei Variablen
    gp 6.4.8 Typsicherere Konstanten
    gp 6.4.9 Statische Blöcke
  gp 6.5 Objekte anlegen und zerstören
    gp 6.5.1 Konstruktoren schreiben
    gp 6.5.2 Einen anderen Konstruktor der gleichen Klasse aufrufen
    gp 6.5.3 Initialisierung der Objekt- und Klassenvariablen
    gp 6.5.4 Finale Werte im Konstruktor setzen
    gp 6.5.5 Exemplarinitialisierer (Instanzinitialisierer)
    gp 6.5.6 Zerstörung eines Objekts durch den Müllaufsammler
    gp 6.5.7 Implizit erzeugte Stringobjekte
    gp 6.5.8 Zusammenfassung: Konstruktoren und Methoden
  gp 6.6 Veraltete (deprecated) Methoden/Konstruktoren
  gp 6.7 Vererbung
    gp 6.7.1 Vererbung in Java
    gp 6.7.2 Einfach- und Mehrfachvererbung
    gp 6.7.3 Kleidungsstücke modelliert
    gp 6.7.4 Sichtbarkeit
    gp 6.7.5 Das Substitutionsprinzip
    gp 6.7.6 Automatische und Explizite Typanpassung
    gp 6.7.7 Finale Klassen
    gp 6.7.8 Unterklassen prüfen mit dem Operator instanceof
  gp 6.8 Methoden überschreiben
    gp 6.8.1 super: Aufrufen einer Methode aus der Oberklasse
    gp 6.8.2 Nicht überschreibbare Funktionen
    gp 6.8.3 Fehlende kovariante Rückgabewerte
  gp 6.9 Die oberste aller Klassen: Object
    gp 6.9.1 Klassenobjekte
    gp 6.9.2 Hashcodes
    gp 6.9.3 Objektidentifikation mit toString()
    gp 6.9.4 Objektgleichheit mit equals() und Identität
    gp 6.9.5 Klonen eines Objekts mit clone()
    gp 6.9.6 Aufräumen mit finalize()
    gp 6.9.7 Synchronisation
  gp 6.10 Die Oberklasse gibt Funktionalität vor
    gp 6.10.1 Dynamisches Binden als Beispiel für Polymorphie
    gp 6.10.2 Keine Polymorphie bei privaten, statischen und finalen Methoden
    gp 6.10.3 Konstruktoren in der Vererbung
  gp 6.11 Abstrakte Klassen
    gp 6.11.1 Abstrakte Klassen
    gp 6.11.2 Abstrakte Methoden
    gp 6.11.3 Über abstract final
  gp 6.12 Schnittstellen
    gp 6.12.1 Die Mehrfachvererbung bei Schnittstellen
    gp 6.12.2 Erweitern von Interfaces – Subinterfaces
    gp 6.12.3 Vererbte Konstanten bei Schnittstellen
    gp 6.12.4 Vordefinierte Methoden einer Schnittstelle
    gp 6.12.5 CharSequence als Beispiel einer Schnittstelle
  gp 6.13 Innere Klassen
    gp 6.13.1 Geschachtelte Top-Level-Klassen und Schnittstellen
    gp 6.13.2 Mitglieds- oder Elementklassen
    gp 6.13.3 Lokale Klassen
    gp 6.13.4 Anonyme innere Klassen
    gp 6.13.5 Eine Sich-Selbst-Implementierung
    gp 6.13.6 this und Vererbung
    gp 6.13.7 Implementierung einer verketteten Liste
    gp 6.13.8 Funktionszeiger
  gp 6.14 Gegenseitige Abhängigkeiten von Klassen
  gp 6.15 Pakete


Galileo Computing

6.7 Vererbung  downtop

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.


Galileo Computing

6.7.1 Vererbung in Java  downtop

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
{
}

Galileo Computing

6.7.2 Einfach- und Mehrfachvererbung  downtop

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.


Galileo Computing

6.7.3 Kleidungsstücke modelliert  downtop

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
Abbildung

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.


Galileo Computing

6.7.4 Sichtbarkeit  downtop

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.


Galileo Computing

6.7.5 Das Substitutionsprinzip  downtop

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.

Abbildung

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.


Galileo Computing

6.7.6 Automatische und Explizite Typanpassung  downtop

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
  }
}

Galileo Computing

6.7.7 Finale Klassen  downtop

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.


Galileo Computing

6.7.8 Unterklassen prüfen mit dem Operator instanceof  toptop

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.





Copyright © Galileo Press GmbH 2003
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de