Kapitel 6 Eigene Klassen schreiben
Das Gesetz ist der abstrakte Ausdruck des allgemeinen
an und für sich seienden Willens.
- Georg Wilhelm Friedrich Hegel
6.1 Eigene Klassen definieren   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.

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.
6.1.1 Methodenaufrufe und Nebeneffekte  
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.
6.1.2 Argumentübergabe mit Referenzen  
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.
6.1.3 Die this-Referenz  
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:
|
Die this-Referenz löst das Problem, wenn lokale Variablen Objektvariablen verdecken. |
|
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.
6.1.4 Überdeckte Objektvariablen nutzen 
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?
|