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 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.
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.
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 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.
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.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.
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 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.
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.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.
Hier klicken, um das Bild zu Vergrößern
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?
|