04.03.04 Umgang mit mehreren Klassen
Die Theorie zu Attributen und Methoden haben Sie – erst einmal – überstanden. Wir wollen uns das Gelernte an einem etwas größeren Beispiel anschauen. Wir werden Schritt für Schritt die benötigten Klassen erstellen, die Verwendung der Konsole zum kompilieren und ausführen nutzen und uns mit den Techniken im Hintergrund beschäftigen.
Durst im Sommer
Der Rahmen unseres Beispieles ist ein gemütlicher Nachmittag im Englischen Garten in München. Man liegt auf der Wiese und hat sich einen zuvor gefüllten Bierkasten mitgebracht um der Sommerhitze Paroli bieten zu können. Der Kasten selber enthält einzelne Bierflaschen verschiedenster Art, welche wir im Laufe des Nachmittags zur Abkühlung konsumieren wollen.
Aus dieser kurzen Beschreibung wollen wir uns nun ein Programm bauen, welches einen Bierkasten mit Flaschen befüllen kann und mit dem man auch einzelne Flaschen herausnehmen kann, um sie zu trinken. Aus der Beschreibung geht hervor, dass wir wahrscheinlich zwei Klassen benötigen werden – eine Klasse Bierkasten
und eine Klasse Bierflasche
. Zusätzlich testen wir dies dann alles in der Klasse EnglischerGarten
. Alle diese Klassen speichern wir in einem Verzeichnis ab.
Bierflasche
Fangen wir zunächst mit der Bierflasche an. Welche Eigenschaften hat so eine Bierflasche?
In unserem Fall reicht es, wenn die Flasche einen Namen hat und wir den aktuellen Zustand (voll oder leer) wissen. Somit können wir den Zustand unserer Bierflasche über die Attribute name
und istLeer
beschreiben. Weiterhin soll es möglich sein einer Flasche einen Namen zu geben und auch diese Flasche zu trinken. Zum Abschluss soll uns eine weitere Methode über den Zustand in Textform informieren.
Wir haben also für unsere Klasse Bierflasche
folgende Elemente.
Attribute
name
istLeer
Methoden
setzeName
trinkeFlasche
zustand
Der name
soll aus Buchstaben bestehen, weswegen wir hierfür den Typ String
verwenden. Für das Attribut istLeer
reicht die Verwendung des Typs boolean
, welches angibt, ob die Flasche leer ist oder nicht. Die Methode setzeName
soll den Namen der Flache setzen. Also benötigen wir einen Parameter vom Typ String
mit dem Namen. Mit trinkeFlasche
wird der Zustand der Flasche auf „leer getrunken“ gesetzt. Sie benötigt also keinen Übergabeparameter. Die Methode zustand
liefert uns eine Textbeschreibung und muss demzufolge einen Rückgabewert vom Typ String
besitzen. Nach diesen Vorbetrachtungen können wir nun die Klasse Bierflasche
schreiben.
public class Bierflasche { private String name; private boolean istLeer = false; public void setzeName(String aName) { this.name = aName; } public void trinkeFlasche() { this.istLeer = true; } public String zustand() { String fuellstand = ""; if (this.istLeer) { fuellstand = "leere"; } else { fuellstand = "volle"; } return "eine " + fuellstand + " Flasche " + this.name; } }
Die beiden Methoden setzeName
und trinkeFlasche
dürften selbst erklärend sein, da sie lediglich die Werte der Attribute name
und istLeer
verändern. Die Methode zustand
überprüft, ob die Flasche leer ist oder nicht und gibt uns dementsprechend einen String
zurück.
Bierkasten
Analog zu den Vorüberlegungen zu der Bierflasche wollen wir dies nun auch für den Bierkasten tun. Ein Bierkasten besteht aus einer bestimmten Anzahl von Fächern, welche Flaschen beinhalten können. Weiterhin soll es möglich sein, dass wir den Bierkasten befüllen und einzelne Flaschen entnehmen können. Damit wir wissen wo noch Platz für weitere Flaschen im Kasten ist, brauchen wir ein Attribut, welches sich die aktuelle Position merkt. Der Bierkasten selbst soll uns per Textausgabe über seinen aktuellen Zustand informieren. Daraus ergeben sich folgende Elemente für diese Klasse.
Attribute
inhalt
position
Methoden
setzeInhalt
neueFlasche
gibFlasche
ausgabe
Der inhalt
besteht aus einem Feld von Elementen des Typs Bierflasche
und wird somit als Array deklariert. Das Attribut position
zeigt immer auf das nächste leere Fach innerhalb unseres Bierkastens. Da es sich dabei um ganze Zahlen handelt, ist dieses Attribut vom Typ int
. Die Methode setzeInhalt
soll es ermöglichen die maximal mögliche Anzahl an Fächern zu setzen. Dies erfolgt per Übergabe eines Parameters vom Typ int
. Mit neueFlasche
wird eine neue Flasche in den Kasten gestellt, dazu braucht diese Methode einen Parameter vom Typ Bierflasche
. Um eine Flasche aus dem Kasten zu nehmen, benötigen wir die Angabe der Position aus welchem Fach die Flasche entnommen werden soll. Zusätzlich wird diese Flasche noch zurückgegeben. Unsere Methode gibtFlasche
enthält also einen Parameter vom Typ int
für die Position und als Rückgabetyp Bierflasche
. Mit ausgabe
soll dann der Inhalt unseres Bierkastens auf der Konsole ausgegeben werden. Sie benötigt dafür keine Parameter. Dies setzen wir nun alles in einer Klasse namens Bierkasten
um.
public class Bierkasten { private Bierflasche[] inhalt; private int position = 0; public void setzeInhalt(int aInhalt) { this.inhalt = new Bierflasche[aInhalt]; } public void neueFlasche(Bierflasche aFlasche) { this.inhalt[this.position] = aFlasche; this.position++; } public Bierflasche gibFlasche(int aPos) { return this.inhalt[aPos]; } public void ausgabe() { System.out.println("Dieser Bierkasten enthaelt:\n"); for (int i = 0; i < this.inhalt.length; i++) { System.out.println(this.inhalt[i].zustand()); } System.out.println(); } }
Das Attribut inhalt
ist ein Array mit dem Typ Bierflasche
, dies ist somit unser Container für die einzelnen Flaschen. Mit position
soll immer auf das nächste freie Fach im Kasten gezeigt werden. Dies ist zu Beginn stets das Fach 0. Mit setzeInhalt
wird die maximale Anzahl an Fächern gesetzt. Das heißt, unser Bierkasten kann maximal aInhalt
viele Bierflaschen enthalten. Die Methode neueFlasche
soll die als Parameter übergebene Bierflasche in das nächste freie Fach abstellen. Dazu wird die nächste freie Stelle des Arrays inhalt
bestimmt und die übergebene Flasche dort untergebracht. Danach wird die Position des nächsten freien Faches um eins erhöht. Sollte Ihnen bei der Arbeit mit Arrays noch etwas unklar sein, lesen Sie bitte noch einmal Kapitel 02.09 Arrays. Mit gibFlasche
wird uns die Flasche an der übergebenen Position aPos
des Arrays inhalt
zurück gegeben. Die ausgabe
gibt den kompletten Inhalt des Bierkastens aus, indem wir mit einer for-Schleife über das Attribut inhalt
iterieren. Innerhalb dieser Schleife holen wir uns die einzelnen Elemente vom Typ Bierflasche
und verwenden deren Methode zustand
für die Ausgabe.
Englischer Garten
Wir wollen nun, nach Fertigstellung der Klassen Bierflasche
und Bierkasten
, diese auch testen. Dazu schreiben wir eine dritte Klasse mit dem Namen EnglischerGarten
.
public class EnglischerGarten { public static void main(String[] args) { // Kasten aus dem Keller holen Bierkasten kasten = new Bierkasten(); kasten.setzeInhalt(3); // Im Kühlschrank schauen, was noch an Bier da ist Bierflasche hacker = new Bierflasche(); Bierflasche augustiner = new Bierflasche(); Bierflasche dachs = new Bierflasche(); hacker.setzeName("Hacker-Pschorr - Hubertus Bock"); augustiner.setzeName("Augustiner Edelstoff"); dachs.setzeName("Dachsbraeu - Ulimator"); // gefundenes Bier in den Kasten stellen kasten.neueFlasche(hacker); kasten.neueFlasche(augustiner); kasten.neueFlasche(dachs); // in den Kasten schauen kasten.ausgabe(); // ein Bier trinken Bierflasche bier = kasten.gibFlasche(1); bier.trinkeFlasche(); // wieder in den Kasten schauen kasten.ausgabe(); } }
Begonnen wird mit dem Erzeugen eines Bierkastens namens kasten
. Danach setzen wir den Inhalt auf maximal drei Flaschen. Zum einen soll der Tag im Englischen Garten uns in guter Erinnerung bleiben und zum anderen soll dieses Beispiel kompakt und übersichtlich bleiben.
Wir schauen nun nach, was wir noch an Bier da haben, um es in den Kasten zu stellen. Dies sind hacker
, augustiner
und dachs
. Alle Bierflaschen bekommen einen Namen und werden in den Kasten gestellt. Danach lassen wir uns den Inhalt des Kastens anzeigen. Um unseren Durst zu stillen, holen wir uns aus dem Kasten mit der Methode gibFlasche
die Flasche an der Fachposition 1. Dieses Bier trinken wir. Mit der erneuten Ausgabe des Kasteninhaltes sehen wir die Veränderung.
Kompilierung und Ausführung
Wie zu Beginn erwähnt, sollten alle Klasse in einem gemeinsamen Verzeichnis gespeichert werden. Dieses sollte nach Fertigstellung unserer drei Klassen folgenden Inhalt haben.
- Bierflasche.java
- Bierkasten.java
- EnglischerGarten.java
Wir möchten nun diese Quellcode Dateien zu Java Bytecode kompilieren.
Dazu öffnen Sie bitte ihr Terminal bzw. ihre Konsole und wechseln in das entsprechende Verzeichnis.
Sind wir im richtigen Verzeichnis angelangt, benutzen wir den Java-Compiler javac
.
javac *.java
Wir lassen uns mit javac *.java
gleich alle Dateien kompilieren, welche die Endung „java“ besitzen. Unser Verzeichnis bekommt daraufhin drei weitere Dateien.
- Bierflasche.class
- Bierkasten.class
- EnglischerGarten.class
Um unser Programm nun zu starten, müssen wir dem Java Interpreter java
die Klasse als Argument überreichen, welche die main
Methode enthält.
java EnglischerGarten
Beachten Sie bitte, dass Sie bei der Angabe der Startklasse keine Endung mit angeben.
Dies würde einen Fehler erzeugen. Hiermit versucht der Interpreter die Klasse class
in einem Unterverzeichnis „EnglischerGarten“ zu finden, welche ja nicht existiert. Mehr dazu finden Sie in Kapitel 04.03.10 Pakete.
java EnglischerGarten.class
Exception in thread "main" java.lang.NoClassDefFoundError: EnglischerGarten/class
Haben wir alles richtig gemacht, erscheint zweimal die Ausgabe unseres Bierkasteninhaltes.
java EnglischerGarten
Dieser Bierkasten enthaelt:
eine volle Flasche Hacker-Pschorr - Hubertus Bock
eine volle Flasche Augustiner Edelstoff
eine volle Flasche Dachsbraeu - Ulimator
Dieser Bierkasten enthaelt:
eine volle Flasche Hacker-Pschorr - Hubertus Bock
eine leere Flasche Augustiner Edelstoff
eine volle Flasche Dachsbraeu - Ulimator
Sie sehen an der zweiten Ausgabe, dass wir die zweite Flasche im Kasten leeren konnten.
Fazit
Dies war einmal ein etwas ausführlicheres Beispiel zu den Klassen. Ziel war es Ihnen zu zeigen wie Sie mit mehreren Klassen gleichzeitig arbeiten können. Die Verwendung unterschiedlicher Objektmethoden mit und ohne Parameter bzw. Rückgabetyp wurde ebenfalls demonstriert. Weiterhin haben wir unsere Attribute in den Klassen mit dem Schlüsselwort private
gekapselt, um zu verhindern, dass man von außerhalb darauf zugreifen kann, um diese etwa zu ändern. Wir verwenden in diesem Beispiel stets Methoden die diesen Zugriff übernehmen (siehe Kapitel 03.04.06 Sichtbarkeitsmodifizierer). Zusätzlich haben wir gesehen, wie eine selbst geschriebene Klasse eine andere selbst geschriebene benutzen kann. Dies war der Fall, als wir den Inhalt der Klasse Bierkasten
als Array des Typs Bierflasche
deklariert haben.
Ebenso sollte der Verlauf der Quellcode Erstellung, dem Kompilieren und der Ausführung von Programmen auf der Konsole noch einmal vor Augen geführt werden.
Um dieses Beispiel kompakt und übersichtlich zu halten, wurde auf notwendige Sicherheitsüberprüfungen verzichtet. Zum Beispiel müssten alle Zugriffe auf ein Array dahin gehend überprüft werden, ob diese nicht über die Arraygrenzen hinaus darauf zugreifen.
Hi Stefan
Beim Bierkasten
22. System.out.println(„Dieser Bierkasten enthaelt: n“);
das ’n‘ am Schluß soll sicher ein ‚ \n‘ sein oder ?
Hi Roland,
da haben wir aber einen aufmerksamen Leser gewonnen. Danke für den Hinweis, das ist natürlich vollkommen korrekt! Ich habe das Kapitel ausgebessert.
Gruß
Stefan