Das Gesetz ist der abstrakte Ausdruck des allgemeinen an und für sich seienden Willens. – Georg Wilhelm Friedrich Hegel
6 Eigene Klassen schreiben
6.1 Eigene Klassen mit Eigenschaften deklarieren 

Die Deklaration einer Klasse leitet das Schlüsselwort class ein. Im Rumpf der Klasse lassen sich deklarieren:
- Attribute (Variablen)
- Methoden
- Konstruktoren
- Klassen- sowie Exemplar-Initialisierer
- innere Klassen beziehungsweise innere Schnittstellen
Wir wollen die Konzepte der Klassen und Schnittstellen an einem kleinen Spiel verdeutlichen. Beginnen wir mit dem Spieler, den die Klasse Player repräsentieren soll.
Listing 6.1 com/tutego/insel/game/v1/Player.java, Player
class Player { }
Die Klasse hat einen automatischen Konstruktor, sodass ein Exemplar unserer Klasse sich jetzt schon mit new Player() erzeugen ließe.
6.1.1 Attribute deklarieren 

Diese Player-Klasse hat bisher keine Attribute und kann bisher nichts. Geben wir dem Spieler zwei Attribute: eines für den Namen und ein zweites für einen Gegenstand, den er trägt. Die Datentypen sollen beide String sein.
Listing 6.2 com/tutego/insel/game/v2/Player.java, Player
class Player { String name; String item; }
|
Eine zweite Klasse Playground erzeugt in der main()-Funktion für den mutigen Spieler ein Player-Objekt, schreibt und liest die Attribute.
Listing 6.3 com/tutego/insel/game/v2/Playground.java, Playground
class Playground { public static void main( String[] args ) { Player p = new Player(); p.name = "Mutiger Manfred"; p.item = "Schlips"; System.out.printf( "%s nimmt einen %s mit.", p.name, p.item ); } }
Initialisierung von Attributen
Anders als lokale Variablen initialisiert die Laufzeitumgebung alle Attribute mit einem Standardwert:
- 0 bei numerischen Werten und char
- false bei boolean
- null bei Referenzvariablen
Gefällt uns das nicht, lassen sich die Variablen mit einem Wert belegen.
class Player
{
String name = "";
}
6.1.2 Methoden deklarieren 

Zu Attributen gesellen sich Methoden, die üblicherweise auf den Objektvariablen arbeiten. Geben wir dem Player zwei Funktionen. Die erste Funktion clearName() soll den Namen zurück auf den Leerstring "" setzen. Die zweite Methode hasCompoundName() soll verraten, ob der Spielername aus einem Vor- und Nachnamen zusammengesetzt ist. Der Name »Parry Hotter« ist zum Beispiel zusammengesetzt, »Spuckiman« aber nicht.
Listing 6.4 com/tutego/insel/game/v3/Player.java, Player
class Player { String name = ""; String item = ""; void clearName() { name = ""; } boolean hasCompoundName() { return (name == null) ? false : name.contains( " " ); } }
Testen wir die Methode mit zwei Spielern:
Listing 6.5 com/tutego/insel/game/v3/Playground.java, main()
Player parry = new Player(); parry.name = "Parry Hotter"; System.out.printf( "%s has compound name: %b%n", parry.name, parry.hasCompoundName() ); Player spucki = new Player(); spucki.name = "Spuckiman"; System.out.printf( "%s has compound name: %b%n", spucki.name, spucki.hasCompoundName() ); spucki.clearName(); System.out.printf( "Spuckis name is: '%s'%n", spucki.name );
Wie zu erwarten, ist aus Ausgabe:
Parry Hotter has compound name: true Spuckiman has compound name: false Spuckis name is: ''





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 hasCompoundName() direkt auf das nötige Attribut name zu, um die Programmlogik auszuführen. Dies wird oft für Nebeneffekte (Seiteneffekte) genutzt. Die Methode clearName() ändert ausdrücklich eine Objektvariable und verändert so den Zustand des Objekts. hasCompoundName() liest dagegen nur den Zustand, modifiziert ihn aber nicht. Methoden, die Zustände ändern, sollten das in der API-Beschreibung entsprechend dokumentieren.
6.1.3 Die this-Referenz 

In jeder Objektmethode und jedem Konstruktor steht eine Referenz mit dem Namen this bereit, die auf das eigene Exemplar zeigt. Mit dieser this-Referenz lassen sich elegante Lösungen realisieren, wie die folgenden Beispiele zeigen:
- Die this-Referenz löst das Problem, wenn lokale Variablen Objektvariablen verdecken.
- Mit der this-Referenz lässt sich einer anderen Methode eine Referenz auf uns selbst geben.
- Liefert eine Methode als Rückgabe die this-Referenz auf das aktuelle Objekt, lassen sich Methoden der Klasse einfach hintereinander setzen.
StringBuilder sb = new StringBuilder();
sb.append( "Microsoft kauft Ebay" ).append( '?' ); Jedes append() liefert das StringBuilder-Objekt, auf dem es aufgerufen wird. |
Kaskadierbare Methoden
Dem Player soll über zwei Methoden setName() und setItem() Name und Gegenstand gesetzt werden können. Beide Methoden liefern ihr eigenes Player-Objekt zurück.
Listing 6.6 com/tutego/insel/game/v4/Player.java, Player
class Player { String name = ""; String item = ""; Player setName( String n ) { name = n; return this; } Player setItem( String i ) { item = i; return this; } String id() { return name + " has " + item; } }
Erzeugen wir einen Player, und kaskadieren wir drei Methoden:
Listing 6.7 com/tutego/insel/game/v4/Playground.java, main()
String s = new Player().setName( "Parry" ).setItem( "helmet" ).id();
System.out.println( s ); // Parry has helmet
Der Ausdruck new Player() liefert eine Referenz, die wir sofort für den Methodenaufruf nutzen. Da setName() wiederum eine Objektreferenz vom Typ Person liefert, ist setItem() möglich. Die Verschachtelung von setName().setItem() bewirkt, dass Name und Gegenstand gesetzt wird und der nächste Methodenaufruf in der Kette über this eine Referenz auf dasselbe Objekt, aber mit verändertem internen Zustand bekommt. Der abschließende Aufruf von id() unterbricht die Kette, weil die Methode ein String zurückgibt.
|
Ü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. Die this-Referenz kann auf das aktuelle Objekt zugreifen und entsprechend mit dem Punkt-Operator auf einzelne Variablen des Objekts. Häufiger Einsatzort sind Parameter, die genauso genannt werden wie die Exemplarvariablen, um damit eine starke Zugehörigkeit auszudrücken.
Schreiben wir dazu die Methode setName() um:
String name; // Objektvariable name Player setName( String name ) // Lokale Parametervariable name { this.name = name; return this; }
Der an setName() übergebene Wert soll die Objektvariable name initialisieren. Genau dann, wenn eine lokale Variable deklariert wird und sie eine Objekt- oder Klassenvariable überlagert, wird beim Zugriff die lokale Variable genutzt. Also würde im Rumpf der Methode eine Zuweisung wie name = "" die lokale Variable, also den aktuellen Parameterwert überschreiben. Erst mit this.name greifen wir auf die Objektvariable direkt zu, sodass this.name = name; die Objektvariable korrekt mit dem Argument initialisiert.