Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
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 Pakete
gp 6.3.1 Hierarchische Strukturen
gp 6.3.2 Paketnamen
gp 6.3.3 Eine Verzeichnisstruktur für eigene Projekte
gp 6.4 Privatsphäre und Sichtbarkeit
gp 6.4.1 Wieso nicht freie Methoden und Variablen für alle?
gp 6.4.2 Privat ist nicht ganz privat. Es kommt darauf an, wer's sieht
gp 6.4.3 Zugriffsmethoden für Attribute definieren
gp 6.4.4 Zusammenfassung zur Sichtbarkeit
gp 6.4.5 Sichtbarkeit in der UML
gp 6.5 Statische Methoden und Variablen
gp 6.5.1 Warum statische Eigenschaften sinnvoll sind
gp 6.5.2 Statische Eigenschaften mit static
gp 6.5.3 Statische Eigenschaften als Objekteigenschaften nutzen
gp 6.5.4 Statische Eigenschaften und Objekteigenschaften
gp 6.5.5 Statische Variablen zum Datenaustausch
gp 6.5.6 Warum die Groß- und Kleinschreibung wichtig ist
gp 6.5.7 Konstanten mit dem Schlüsselwort final bei Variablen
gp 6.5.8 Problem mit finalen Klassenvariablen
gp 6.5.9 Typsicherere Konstanten
gp 6.5.10 Statische Blöcke
gp 6.6 Objekte anlegen und zerstören
gp 6.6.1 Konstruktoren schreiben
gp 6.6.2 Einen anderen Konstruktor der gleichen Klasse aufrufen
gp 6.6.3 Initialisierung der Objekt- und Klassenvariablen
gp 6.6.4 Finale Werte im Konstruktor setzen
gp 6.6.5 Exemplarinitialisierer (Instanzinitialisierer)
gp 6.6.6 Zerstörung eines Objekts durch den Müllaufsammler
gp 6.6.7 Implizit erzeugte String-Objekte
gp 6.6.8 Zusammenfassung: Konstruktoren und Methoden
gp 6.7 Veraltete (deprecated) Methoden/Konstruktoren
gp 6.8 Vererbung
gp 6.8.1 Vererbung in Java
gp 6.8.2 Einfach- und Mehrfachvererbung
gp 6.8.3 Gebäude modelliert
gp 6.8.4 Konstruktoren in der Vererbung
gp 6.8.5 Sichtbarkeit
gp 6.8.6 Das Substitutionsprinzip
gp 6.8.7 Automatische und explizite Typanpassung
gp 6.8.8 Finale Klassen
gp 6.8.9 Unterklassen prüfen mit dem Operator instanceof
gp 6.8.10 Methoden überschreiben
gp 6.8.11 super: Aufrufen einer Methode aus der Oberklasse
gp 6.8.12 Nicht überschreibbare Funktionen
gp 6.8.13 Fehlende kovariante Rückgabewerte
gp 6.9 Die oberste aller Klassen: Object
gp 6.9.1 Klassenobjekte
gp 6.9.2 Objektidentifikation mit toString()
gp 6.9.3 Objektgleichheit mit equals() und Identität
gp 6.9.4 Klonen eines Objekts mit clone()
gp 6.9.5 Hashcodes
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 Polymorphie bei Konstruktoraufrufen
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 Ein Polymorphie-Beispiel mit Schnittstellen
gp 6.12.2 Die Mehrfachvererbung bei Schnittstellen
gp 6.12.3 Erweitern von Interfaces - Subinterfaces
gp 6.12.4 Vererbte Konstanten bei Schnittstellen
gp 6.12.5 Vordefinierte Methoden einer Schnittstelle
gp 6.12.6 CharSequence als Beispiel einer Schnittstelle
gp 6.13 Innere Klassen
gp 6.13.1 Statische innere 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

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 definierendowntop

Die Deklaration einer Klasse wird durch das Schlüsselwort class eingeleitet. Wir wollen das am Beispiel der Klasse Disko darstellen. Diese einfache Klasse definiert Attribute wie die Anzahl von Personen (int anzahlPersonen), die sich in der Disko aufhalten, und die Größe der Disko in Quadratmetern (int quadratmeter). Des Weiteren berechnen wir den Spaßfaktor einer Disko aus der Anzahl der Personen und der Quadratmeter durch eine obskure willkürliche Formel.

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

Abbildung 6.1 UML-Diagramme für eine Disko

Zu unserer Disko-Klasse können wir ein konkretes Java-Programm angeben.

Listing 6.1 v1/Disko.java

package v1;
public class Disko
{
  int anzahlPersonen;    // Anzahl Personen in der Disko
  int quadratmeter;      // Größe der Disko
  /**
   * Person kommt in die Disko.
   */
  void personRein()
  {
    anzahlPersonen++;
  }
  /**
   * Person verlässt die Disko.
   */
  void personRaus()
  {
    if ( anzahlPersonen > 0 )
    anzahlPersonen--;
  }
  /**
   * Liefert Anzahl Personen in der Disko.
   * 
   * @return Anzahl Personen.
   */
  int anzahlPersonen()
  {
    return anzahlPersonen;
  }
  
  /**
   * Liefert den Unterhaltungswert der Disko.
   * 
   * @return  Unterhaltungswert.
   */
  int unterhaltungswert()
  {
    return (int) (anzahlPersonen * Math.sqrt( quadratmeter )); 
  }
}

Die angegebene Klasse enthält die Methode zum Erhöhen und Verringern der Personenanzahl in der Disko und zum Berechnen des Spaßfaktors. Die beiden Attribute sind öffentlich und können von außen von jedem genutzt werden.

Eine andere Klasse DiskoFieber soll in der main()-Funktion ein Disko-Objekt erzeugen und die Methoden zum Testen aufrufen.

Listing 6.2 v1/DiskoFieber.java

package v1;
public class DiskoFieber
{
  public static void main( String args[] )
  {
    Disko abzappler = new Disko();
    abzappler.quadratmeter = 5;
    System.out.println( "Fun: " + abzappler.unterhaltungswert() );
    abzappler.personRein();
    abzappler.personRein();
    System.out.println( abzappler.anzahlPersonen() );    // 2
  
    System.out.println( "Fun: " + abzappler.unterhaltungswert() );
  }
}

Um dem neu angelegten Disko-Objekt einen Besenkammer-Charakter zu geben, setzen wir die Anzahl Quadratmeter auf 5. Die Nachricht (auch Botschaft) unterhaltungswert() wird an das gewünschte Exemplar der Klasse Disko geschickt. In der Konsolenausgabe erfahren wir dann, dass der Unterhaltungswert eine Disko mit keiner Person 0 ist. Lassen wir zwei Personen rein, so ändert sich der Unterhaltungswert.


Galileo Computing

6.1.1 Methodenaufrufe und Nebeneffektedowntop

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 unterhaltungswert() direkt auf die nötigen Attribute zu. Dies wird oft für Nebeneffekte (Seiteneffekte) genutzt. Eine Methode wie personRein() ändert ausdrücklich eine Objektvariable und verändert so den Zustand des Objekts. personRaus() liest nur den Zustand aus und gibt in nach außen frei.


Galileo Computing

6.1.2 Argumentübergabe mit Referenzendowntop

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.3 DiskoFueller.java

class PersonenDisko
{
  int anzahlPersonen;
}
class PinkFloyd
{
  static void fuellSie( PersonenDisko d )
  {
    d.anzahlPersonen = 1000;
  }
}
public class DiskoFueller
{
  public static void main( String args[] )
  {
    PersonenDisko partykracher = new PersonenDisko();
    partykracher.anzahlPersonen = 2;
    System.out.println( partykracher.anzahlPersonen );  // 2
    PinkFloyd.fuellSie( partykracher );
    System.out.println( partykracher.anzahlPersonen);  // 10000
  }
}

Das Beispiel zeigt eine einfache Disko (PersonenDisko), die durch prominente Gäste gefüllt wird. Die Objektreferenz, die an fuellSie() übergeben wird, lässt eine Attributänderung im Disko-Objekt zu. Referenziert die Variable partykracher ein Disko-Objekt, findet die Änderung in der Methode fuellSie() statt, da die Methode das Objekt über eine Kopie der Objektreferenz in der Variablen d 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 primitiver Typ an eine Funktion übergeben, so gibt es nur Veränderungen in dieser Methode am aktuellen 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-Referenzdowntop

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 diese Arbeitsweise in den Java-Bibliotheken, etwa bei der Klasse StringBuffer mit der Methode append().

Beispiel Eine Klasse definiert eine Methode personRein(), die den internen Wert einer privaten Variablen anzahlPersonen hochzählt.

Listing 6.4 DiskoZaehler.java

public class DiskoZaehler
{
  private int anzahlPersonen;
  public int getAnzahlPersonen()
  {
    return anzahlPersonen;
  }
  public DiskoZaehler personRein()
  {
    anzahlPersonen++;
    return this;
  }
  public static void main( String args[] )
  {
    DiskoZaehler mausefalle = new DiskoZaehler();
    mausefalle.personRein().personRein().personRein();
    System.out.println( mausefalle.getAnzahlPersonen() );                      // 3
    System.out.println( new DiskoZaehler().personRein().getAnzahlPersonen() ); // 1
  }
}

Aus diesem Beispiel mit der main()-Methode können wir erkennen, dass new DiskoZaehler() eine Referenz liefert, die wir sofort für den Methodenaufruf nutzen. Da personRein() wiederum eine Objektreferenz vom Typ DiskoZaehler liefert, ist getAnzahlPersonen() möglich. Die Verschachtelung von personRein().personRein() 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.5 DiskoThis.java

class DiskoThis
{
  int quadratmeter;
  void setQuadratmeter( int quadratmeter )
  {
    quadratmeter = 12;                 // Zuweisung an lokale Variable quadratmeter
    this.quadratmeter = 12;           // Zuweisung an Objektvariable 
    this.quadratmeter = quadratmeter; // Initialisierung der Objektvariablen
  }
}

Der Methode setQudratmeter() wird ein Wert übergeben, der anschließend die Objektvariablen initialisieren soll. 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. Das zeigt die erste Zeile: quadratmeter = 12 überschreibt den aktuellen Parameterwert der Funktion, der damit verloren ist. Erst mit this.quadratmeter greifen wir auf die Objektvariable direkt zu.

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

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

Eclipse erkennt unsinnige Konstruktionen wie anzahlPersonen = anzahlPersonen.

Ein this-Problem

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

void setAnzahlPersonen( int anzahlPerson )
{
  this.anzahlPersonen = anzahlPersonen;
}

Die Methode kompiliert, doch sie enthält einen logischen Fehler. Erkannt? Die Parameter-Variable heißt anzahlPerson, müsste aber eigentlich anzahlPersonen heißen. Der Fehler fällt so erst nicht auf, da die Objektvariable anzahlPersonen einfach mit sich selbst überschrieben wird - glücklicherweise merkt das Eclipse. Doch wie perfekt programmiert man, wenn man 10.000 Mal programmiert hat?





Copyright (c) Galileo Press GmbH 2004
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