Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
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 Attribute
    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 Statische Blöcke
  gp 6.5 Konstanten und Aufzählungen
    gp 6.5.1 Konstanten mit dem Schlüsselwort final bei Variablen
    gp 6.5.2 Problem mit finalen Klassenvariablen
    gp 6.5.3 Typsicherere Konstanten
    gp 6.5.4 Aufzählungen und enum in Java 5
    gp 6.5.5 enum-Konstanten in switch
  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 Private Konstruktoren, Utility-Klassen, Singleton und Fabriken
    gp 6.6.9 Zusammenfassung: Konstruktoren und Methoden
  gp 6.7 Vererbung
    gp 6.7.1 Vererbung in Java
    gp 6.7.2 Einfach- und Mehrfachvererbung
    gp 6.7.3 Gebäude modelliert
    gp 6.7.4 Konstruktoren in der Vererbung
    gp 6.7.5 Sichtbarkeit
    gp 6.7.6 Das Substitutionsprinzip
    gp 6.7.7 Automatische und explizite Typanpassung
    gp 6.7.8 Array-Typen
    gp 6.7.9 Finale Klassen
    gp 6.7.10 Unterklassen prüfen mit dem Operator instanceof
    gp 6.7.11 Methoden überschreiben
    gp 6.7.12 super: Aufrufen einer Methode aus der Oberklasse
    gp 6.7.13 Nicht überschreibbare Funktionen
  gp 6.8 Die oberste aller Klassen: Object
    gp 6.8.1 Klassenobjekte
    gp 6.8.2 Objektidentifikation mit toString()
    gp 6.8.3 Objektgleichheit mit equals() und Identität
    gp 6.8.4 Klonen eines Objekts mit clone()
    gp 6.8.5 Hashcodes
    gp 6.8.6 Aufräumen mit finalize()
    gp 6.8.7 Synchronisation
  gp 6.9 Die Oberklasse gibt Funktionalität vor
    gp 6.9.1 Dynamisches Binden als Beispiel für Polymorphie
    gp 6.9.2 Keine Polymorphie bei privaten, statischen und finalen Methoden
    gp 6.9.3 Polymorphie bei Konstruktoraufrufen
  gp 6.10 Abstrakte Klassen
    gp 6.10.1 Abstrakte Klassen
    gp 6.10.2 Abstrakte Methoden
    gp 6.10.3 Über abstract final
  gp 6.11 Schnittstellen
    gp 6.11.1 Ein Polymorphie-Beispiel mit Schnittstellen
    gp 6.11.2 Die Mehrfachvererbung bei Schnittstellen
    gp 6.11.3 Erweitern von Interfaces – Subinterfaces
    gp 6.11.4 Vererbte Konstanten bei Schnittstellen
    gp 6.11.5 Vordefinierte Methoden einer Schnittstelle
    gp 6.11.6 CharSequence als Beispiel einer Schnittstelle
    gp 6.11.7 Die Schnittstelle Iterable
  gp 6.12 Innere Klassen
    gp 6.12.1 Statische innere Klassen und Schnittstellen
    gp 6.12.2 Mitglieds- oder Elementklassen
    gp 6.12.3 Lokale Klassen
    gp 6.12.4 Anonyme innere Klassen
    gp 6.12.5 Eine Sich-Selbst-Implementierung
    gp 6.12.6 this und Vererbung
    gp 6.12.7 Implementierung einer verketteten Liste
    gp 6.12.8 Funktionszeiger
  gp 6.13 Generische Datentypen
    gp 6.13.1 Einfache Klassenschablonen
    gp 6.13.2 Einfache Methodenschablonen
    gp 6.13.3 Generics und Vererbung
    gp 6.13.4 Einschränken der Typen
    gp 6.13.5 Umsetzen der Generics, Typlöschung und Raw-Types
    gp 6.13.6 Wildcards
  gp 6.14 Die Spezial-Oberklasse Enum
    gp 6.14.1 Methoden auf Enum-Objekten
    gp 6.14.2 enum mit eigenen Konstruktoren und Methoden
  gp 6.15 Gegenseitige Abhängigkeiten von Klassen
  gp 6.16 Veraltete (deprecated) Methoden/Konstruktoren

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 definieredowntop

Die Deklaration einer Klasse wird durch das Schlüsselwort class eingeleitet. Im Rumpf der Klasse lassen sich Variablen deklarieren und Methoden definieren. Zusätzlich sind erlaubt – und zu einem späteren Zeitpunkt erklärt – Konstruktoren, Klassen- sowie Exemplarinitialisierer und innere Klassen beziehungsweise innere Schnittstellen.

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.

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

Um schnell von einer Methode (oder Variablen) zur anderen zu navigieren, lässt sich mit (Ctrl)+(O) ein Outline anzeigen (das ist dieselbe Ansicht wie im View Outline). Im Unterschied zur View lässt sich in diesem kleinen gelben Fenster mit den Cursor-Tasten navigieren und ein Return befördert uns zu der angewählten Funktion oder zur Variablen. Wird in der Ansicht erneut (Ctrl)+(O) gedrückt, finden sich dort auch die in den Oberklassen definierten Eigenschaften. Sie sind grau, und zusätzlich finden sich hinter den Eigenschaften die Klassennamen.

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

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 Nebeneffektdowntop

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 Referenzedowntop

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 – den Argumenten – 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-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 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 nutzen  toptop

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