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


Java ist auch eine Insel (3. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel
gp Kapitel 6 Eigene Klassen schreiben
  gp 6.1 Eigene Klassen definieren
    gp 6.1.1 Methodenaufrufe und Nebeneffekte
    gp 6.1.2 Argumentübergabe mit Referenzen
    gp 6.1.3 Die this-Referenz
    gp 6.1.4 Überdeckte Objektvariablen nutzen
  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.3.5 Sichtbarkeit in der UML
  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 String-Objekte
    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

Kapitel 6 Eigene Klassen schreiben

Das Gesetz ist der abstrakte Ausdruck des allgemeinen
an und für sich seienden Willens.
- Georg Wilhelm Friedrich Hegel


Galileo Computing

6.1 Eigene Klassen definieren downtop

Die Deklaration einer Klasse wird durch das Schlüsselwort class eingeleitet. Wir wollen das am Beispiel der Klasse Socke darstellen. Diese einfache Klasse definiert Daten und Methoden. Die Signatur einer Methode bestimmt ihren Namen und ihre Parameterliste. Die Socke-Klasse speichert wesentliche Attribute, die jeder Socke zugeordnet werden.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 6.1 UML-Diagramme für Socken

sockenstellensichvor.pcxZu unserer Socken-Klasse wollen wir ein konkretes Java-Programm angeben. Eine Klasse Socke definiert die Attribute gewicht und farbe, und die andere Klasse erzeugt in der main()-Funktion ein Socke-Objekt. Wir erkennen am Schlüsselwort private, dass es Daten geben kann, die nach außen nicht sichtbar sind, da der Compiler die Sichtbarkeit erzwingt. Innerhalb der Klasse lässt sich das Attribut selbstverständlich verwenden. Wer außer der Klasse sollte es sonst können?

Listing 6.1 SockeDemo.java

class Socke
{
  public String   farbe;
  public int      gewicht;
  private boolean istTrocken;
  public void trockne()
  {
    istTrocken = true;
  }
  public void wasche()
  {
    istTrocken = false;
  }
  public boolean istTrocken()
  {
    return istTrocken;
  }
}
public class SockeDemo
{
  public static void main( String args[] )
  {
    Socke stinki;
    stinki = new Socke();
    stinki.farbe = "rot";
    stinki.gewicht = 565;
    stinki.wasche();
    System.out.println( "Ist die Socke trocken? " +
                        stinki.istTrocken() );
  }
}

Die angegebene Klasse enthält die Methode trockne() und zwei Objektvariablen. Um ein neu angelegtes Socke-Objekt zum Waschen aufzufordern, ruft die main()-Methode die Methode wasche() für das erzeugte Objekt auf: Die Nachricht (auch Botschaft) wasche() wird an das gewünschte Exemplar der Klasse Socke geschickt. In der Konsolenausgabe erfahren wir dann über istTrocken(), ob die Socke feucht ist oder nicht. istTrocken() gibt ein boolean zurück. Damit kapselt die Methode die private-Variable istTrocken, auf die kein Zugriff von außen möglich ist. Das Beispiel zeigt, dass ein Attribut und eine Methode den gleichen Namen besitzen können.


Galileo Computing

6.1.1 Methodenaufrufe und Nebeneffekte downtop

Alle Variablen und Methoden einer Klasse sind in der Klasse selbst sichtbar. Das heißt, innerhalb einer Klasse werden die Objektvariablen und Funktionen mit ihrem Namen verwendet. Somit greift die Funktion trocknen() direkt auf die möglichen Attribute zu. Das wird oft für Nebeneffekte (Seiteneffekte) genutzt. Eine Methode wie trocknen() ändert ausdrücklich eine Objektvariable und verändert so den Zustand des Objekts.


Galileo Computing

6.1.2 Argumentübergabe mit Referenzen downtop

In Java werden alle Datentypen als Wert übergeben (engl. copy by value). Das heißt, die formalen Parameter sind lokale Variablen des Unterprogramms, die mit den aktuellen Parameterwerten initialisiert werden. Objekte werden bei der Parameterübergabe nicht kopiert, sondern es wird ihre Referenz übergeben. Die aufgerufene Methode kann dann das Objekt verändern. Dies muss in der Dokumentation der Methode angegeben werden.

Listing 6.2 ZuOftGewaschen.java

class WaschSocke
{
  String farbe;
}
class Waschmaschine
{
  static void auswaschen( WaschSocke s )
  {
    s.farbe = "weiß";
  }
}
public class ZuOftGewaschen
{
  public static void main( String args[] )
  {
    WaschSocke omisSocke = new WaschSocke();
    omisSocke.farbe = "schwarz";
    System.out.println( omisSocke.farbe );  // schwarz
    Waschmaschine.auswaschen( omisSocke );
    System.out.println( omisSocke.farbe );  // weiß
  }
}

Das Beispiel zeigt eine Socke, die ihre Farbe durch Auswaschen verliert. Die Objektreferenz, die an auswaschen() übergeben wird, lässt eine Attributänderung im Socken-Objekt zu. Zeigt die Referenz schwarz auf ein Socken-Objekt, findet die Änderung in der Methode auswaschen() statt, da die Methode das Objekt über eine Kopie der Objektreferenz unter dem Namen s anspricht. In Java wird, anders als zum Beispiel in C++, bei der Parameterübergabe niemals eine Kopie des übergebenen Objekts angelegt, nur die Objektreferenz wird kopiert und per Wert übergeben.

Wir wollen an dieser Stelle noch einmal den Unterschied zu primitiven Typen hervorheben. Wird ein eingebauter Typ einer Funktion übergeben, so gibt es nur Veränderungen in dieser Methode am Parameter, der ja als lokale Variable behandelt werden kann. Eine Veränderung dieser lokalen Variablen tritt somit nicht nach außen und bleibt lokal.


Galileo Computing

6.1.3 Die this-Referenz downtop

In jedem Konstruktor und jeder Objektmethode einer Klasse existiert eine Referenz mit dem Namen this, die auf das aktuelle Exemplar der Klasse zeigt. Mit dieser this-Referenz lassen sich elegante Lösungen realisieren, wie folgende Beispiele zeigen:

gp  Die this-Referenz löst das Problem, wenn lokale Variablen Objektvariablen verdecken.
gp  Wenn Methoden this-Referenzen liefern, hat das gute Gründe, denn Methoden können einfach hintereinander gesetzt werden. Es gibt viele Beispiele für Arbeitsweisen in den Java-Bibliotheken, etwa bei der Klasse StringBuffer.
Beispiel   Eine Klasse definiert eine Methode inc(), die den internen Wert einer privaten Variablen hochzählt.

Listing 6.3 ThisGoOn.java

public class ThisGoOn
{
  private int value;
  public int getValue() { return value; }
  public ThisGoOn inc()
  {
    value++;
    return this;
  }
  public static void main( String args[] )
  {
    ThisGoOn ref = new ThisGoOn();
    ref.inc().inc().inc();
    System.out.println( ref.getValue() );                  // 3
    System.out.println( new ThisGoOn().inc().getValue() ); // 1
  }
}

Aus diesem Beispiel mit der main()-Methode können wir erkennen, dass new ThisGoOn() eine Referenz liefert, die wir sofort für den Methodenaufruf nutzen. Da inc() wiederum eine Objektreferenz vom Typ ThisGoOn liefert, ist getValue() möglich. Die Verschachtelung von inc().inc() bewirkt, dass immer das interne Attribut erhöht wird und der nächste Methodenaufruf in der Kette eine Referenz auf dasselbe Objekt, aber mit verändertem internem Zustand (= Zählerstand), über this bekommt.


Galileo Computing

6.1.4 Überdeckte Objektvariablen nutzentoptop

Hat eine lokale Variable den gleichen Namen wie eine Objektvariable, so verdeckt sie diese. Das heißt aber nicht, dass auf die äußere Variable nicht mehr zugegriffen werden kann. Mit der this-Referenz kann auf das aktuelle Objekt zugegriffen werden und entsprechend mit dem Punkt-Operator auf einzelne Variablen des Objekts. Häufiger Einsatzort sind Funktions- oder Konstruktorparameter, die genauso genannt werden wie die Exemplarvariablen, um damit eine starke Zugehörigkeit auszudrücken.

Listing 6.4 PunktThisDemo.java

class PunktThisDemo
{
  int x, y;
  void setzePosition( int x, int y )
  {
    x = 12;           // Zuweisung an lokale Variable x
    this.x = 12;      // Zuweisung an Objektvariable x
    this.x = x;       // Initialisierung der Objektvariable
    this.y = y;
  }
}

Der Methode setzePosition() werden zwei Werte übergeben, die anschließend die Objektvariablen initialisieren.

Genau in dem Moment, wo eine lokale Variable deklariert wird und sie eine Objekt- oder Klassenvariable überlagert, wird beim Zugriff auf die lokale Variable verwiesen. Soll die lokale Variable den Wert der Objekt- oder Klassenvariablen annehmen, lässt sich nicht einfach Folgendes schreiben:

class A
{
  int x;
  void foo() {
    x = 1;
    int x = x;        // Fehler
  }
}

Das Problem in der Zeile ist, dass die lokale Variable x mit ihrem eigenen Wert initialisiert werden soll. Das x auf der rechten Seite bezeichnet nicht die Objektvariable x. Denn die Deklaration mit gleichzeitiger Initialisierung ist in Wirklichkeit nichts anderes als eine Kurzschreibweise für

int x;
x = x;

In dem Moment, in dem x deklariert ist, ist jeder Zugriff auf die lokale Variable bezogen. Bei der rechten Seite von x = x handelt es sich um einen Lesezugriff auf eine nicht initialisierte Variable.

Ein this-Problem

Nutzen wir Konstruktionen wie this.variable = variable, so kann das zu einem schwer zu findenden Fehler führen. Das nachfolgende Beispiel zeigt das Problem unter der Annahme, es gebe eine Objektvariable number.

void setNumber( int nummer )
{
  this.number = number;
}

Die Methode kompiliert, doch sie enthält einen logischen Fehler. Erkannt? Die Parameter-Variable heißt nummer, müsste aber number heißen. Der Fehler fällt nicht auf, da number einfach mit sich selbst überschrieben wird. Doch wie perfekt programmiert man, wenn man 10.000 Mal programmiert hat?





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] [Buchkatalog] [Neue Bücher] [Vorschau]

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