fKlassen und Objekte Inhaltsverzeichnis Hypertext-Version Implementierung von Datenbehältern © APSIS GmbH

3. Strukturierung von Klassen

Seite 50

3.1. Prozeduren

Seite 50

3.1.1. Aufruf von Prozeduren

Seite 50

import lehrbuch.*; // (3.1)
public class DreimalFuellen extends Programm {
	private Eimer eimer; // globale Referenz
	private void fuellenUndEntleeren() { // Definition einer Prozedur ¬
		try {
			eimer.fuellen();
			eimer.entleeren();
		}
		catch (VollAusn ausnahme) {}
		catch (LeerAusn ausnahme) {}
	} // Ende der Definition
	public void start() {
		eimer = new Eimer(); // Objekterzeugung
		fuellenUndEntleeren(); // Aufruf der Prozedur ¬
		fuellenUndEntleeren();
		fuellenUndEntleeren(); // Prozedur wurde dreimal aufgerufen
	}
}

3.1.2. Schachtelung von Algorithmen

Seite 51

import lehrbuch.EinEimer; // (3.2)
public class FuenfmalFuellen extends lehrbuch.Programm {
	protected void zweimalFuellen() {
		EinEimer.fuellen();
		EinEimer.entleeren();
		EinEimer.fuellen();
		EinEimer.entleeren();
	}
	public void start() {
		zweimalFuellen(); // ¬
		zweimalFuellen();
		EinEimer.fuellen();
		EinEimer.entleeren();
	}
} // Dieses Programm füllt und entleert einen Datenbehälter fünfmal hintereinander.
public class SechsmalFuellen extends FuenfmalFuellen { // (3.3) ¬
		// Klasse aus dem Programm (3.2) wird erweitert
	public void start() {
		zweimalFuellen();
		zweimalFuellen();
		zweimalFuellen();
	}
}

Übung 3.1: Erweitern Sie die Klasse SechsmalFuellen aus dem Programm (3.3) auf Seite 51. Rufen Sie in Ihrer Klasse die Methode zweimalFuellen zweimal auf. Sie müssen dabei dafür sorgen, dass die notwendigen Klassen (FuenfmalFuellen und SechsmalFuellen) für den Compiler erreichbar sind (d.h. am einfachsten im selben Verzeichnis vorliegen). Was geschieht, wenn zweimalFuellen als private vereinbart wird?

3.1.3. Steuerfluss

Seite 52

public class Steuerfluss extends lehrbuch.Programm { // (3.4)
	private void prozedur1() {
		super.anweisung(3);
	}
	public void start() {
		super.anweisung(6);
		prozedur1();
		prozedur2();
		super.anweisung(9);
		prozedur2();
	}
	private void prozedur2() {
		super.anweisung(13);
		prozedur1();
		super.anweisung(15);
	}
}

Übung 3.2: Schreiben Sie ein möglichst einfaches Programm mit Hilfe der Methode anweisung, in dem die Schachtelungstiefe 3 ist. Protokollieren Sie jeden Aufruf und verfolgen Sie den Steuerfluss mit Hilfe der am Bildschirm sichtbaren Zahlen. Untersuchen Sie die dynamische Reihenfolge der ausgeführten Programmzeilen.

3.1.4. Parametrisierte Prozeduren

Seite 54

import lehrbuch.*; // (3.5)
public class Parametrisiert extends Programm {
	private Eimer ersterEimer, zweiterEimer, dritterEimer; // drei Referenzen
	private void fuellenUndEntleeren(Eimer eimer) throws VollAusn, LeerAusn { // ¬
		// Definition der Prozedur; ihr formaler Parameter ist eimer der Klasse Eimer
		eimer.fuellen();
		eimer.entleeren();
	}
	public void start() {
		try {
			ersterEimer = new Eimer();
			zweiterEimer = new Eimer();
			dritterEimer = new Eimer(); // drei Datenbehälter
			... // evtl. Eimer anzeigen
			fuellenUndEntleeren(ersterEimer); // Aufruf mit Parameter ersterEimer
			fuellenUndEntleeren(zweiterEimer); // Aufruf mit Parameter zweiterEimer
			fuellenUndEntleeren(ersterEimer);
			fuellenUndEntleeren(dritterEimer);
			fuellenUndEntleeren(dritterEimer); // Prozedur wurde fünfmal aufgerufen
		}
		catch (VollAusn ausnahme) { meldung("Eimer schon voll"); }
		catch (LeerAusn ausnahme) { meldung("Eimer noch leer"); }
	}
}

Abb. 3.1: Parametereinsetzung beim Aufruf

Übung 3.3: Vereinbaren Sie Ihren Algorithmus aus der Übung 2.16 auf Seite 46 (d.h. die Anweisungsfolge in der Methode start) als Prozedur mit zwei Parametern vom Typ Kreis. Vereinbaren Sie in Ihrer Klasse nun drei Kreise und rufen Sie Ihre Prozedur dreimal für jeweils zwei Kreise (mit verschiedenen Kombinationen) auf. Verfolgen Sie den Ablauf Ihres Programms von Aufruf zu Aufruf.

Ein Tipp: Lagern Sie die Aufrufe der Methode zeichnen aus Ihrer Prozedur in die start-Methode aus. Dann werden die Kreise nicht immer wieder neu gezeichnet, sondern Sie können die Orte, wo sich Ihr Programm aufhält, leichter identifizieren.

3.1.5. Globale Referenz oder Parameter

Seite 56

import lehrbuch.*; // (3.6)
public class GlobalerEimer extends Programm {
	private Eimer eimer; // globale Referenz ¬
	private void prozedur() throws VollAusn { // parameterlos ¬
		eimer.fuellen(); // oder ein komplexerer Algorithmus
	}
	public void start() {
		try {
			eimer = new Eimer(); // Objekt wird erzeugt
			eimer.anzeigen();
			prozedur(); // Aufruf der Prozedur ¬
			eimer.entleeren();
			eimer = new Eimer(); // ein zweites Objekt wird erzeugt
			prozedur(); // erneuter Aufruf der Prozedur
		}
		catch (VollAusn ausnahme) { super.meldung("Eimer schon voll"); }
		catch (LeerAusn ausnahme) { super.meldung("Eimer noch leer"); }
	}
}
import lehrbuch.*; // (3.7)
public class ParameterEimer extends Programm {
	private void prozedur(Eimer e) throws VollAusn { // parametrisiert ¬
		e.fuellen();// oder ein komplexerer Algorithmus
	}
	public void start() {
		try {
			Eimer eimer; // lokale Referenz
			eimer = new Eimer(); // Objekt wird erzeugt
			eimer.anzeigen();
			prozedur(eimer); // Aufruf der Prozedur ¬
			eimer.entleeren();
			Eimer eimer2 = new Eimer(); // ein zweites Objekt wird erzeugt
			eimer2.anzeigen();
			prozedur(eimer2); // erneuter Aufruf der Prozedur
		}
		catch ... // ähnlich

3.1.6. Wert- und Referenzübergabe

Seite 57

import lehrbuch.*; // (3.8)
public class WertOderReferenz extends Programm {
	private void prozedur(Eimer e) throws VollAusn { // parametrisiert
		e.fuellen(); // Parameterobjekt wird verändert
		e = new Eimer(); // Parameterreferenz wird verändert ¬
		e.anzeigen();
	}
	public void start() {
		try {
			Eimer eimer;
			eimer = new Eimer();
			eimer.anzeigen();
			prozedur(eimer); // Aufruf der Prozedur ¬
			// Eimer wurde in der Prozedur gefüllt, d.h. Parameterobjekt wurde verändert
			// Inhalt der Referenz eimer wurde in der Prozedur nicht verändert
			eimer.entleeren();
		...
private void prozedur(final Eimer e) throws VollAusn {
	e.fuellen(); // Veränderung des Parameterobjekts erlaubt
	e = new Eimer(); // Compiler meldet Fehler: Veränderung ist unterbunden ¬
}
private void prozedur(const Eimer e) throws VollAusn { // nicht Java, C++
	e.fuellen(); // Compiler meldet Fehler: Veränderung des Parameterobjekts verboten ¬
	e = new Eimer();// kein Fehler: Veränderung der Parameterreferenz ist erlaubt
}

Übung 3.4: Zeigen Sie den Unterschied zwischen Wert- und Referenzübergabe mit Kreisen aus der Übung 3.3 auf Seite 56: Verändern Sie in einer Prozedur eines Ihrer Kreisobjekte (das Sie als Parameter übergeben haben) und zeigen Sie, dass das Objekt sich nach dem Aufruf in dem Zustand befindet, wie die Prozedur es hinterlassen hat. Verändern Sie auch eine Ihrer Parameterreferenzen durch Zuweisung in der Prozedur, und zeigen Sie, dass sie sich nach dem Aufruf in dem Zustand befindet, in dem sie vor dem Aufruf war (d.h. die Prozedur hat sie nicht verändert).

3.1.7. Parametrisierte Operationen

Seite 58

import lehrbuch.*; // (3.9)
public class Kopieren extends Programm {
	public void start() {
		Eimer eimer1 = new Eimer();
		Eimer eimer2 = new Eimer();
		try { ... // die beiden Eimer anzeigen, füllen und entleeren
			eimer1.kopieren(eimer2); // ¬
				// Zustand von eimer2 (gefüllt oder entleert) wird in eimer1 übertragen
		} ...

3.2. Pakete

Seite 59

3.2.1. Spezifikation eines Pakets

Seite 59

package lehrbuch; // (3.10)
public class LeereKlasse {
	/** die Klasse exportiert und beinhaltet nichts */
}
public class HalloWelt { // Klasse zum erweitern (Applet) für Textanimation
	... // s. Programm (2.16)
}
public class EinEimer { // Klasse zum erweitern (Applet); statischer Datenbehälter
	/** gleich zu Beginn wird ein Eimer im Fenster angezeigt und animiert */
	public static void fuellen() throws VollFehler;
		// der Eimer wird gefüllt; wenn schon gefüllt, ungeprüfte Ausnahme
	public static void entleeren() throws LeerFehler;
		// der Eimer wird entleert, wenn voll; wenn nicht, ungeprüfte Ausnahme
	/** Reihenfolgebedingungen: Als erster Aufruf darf nur fuellen aufgerufen werden.
		Anschließend dürfen entleeren und fuellen nur abwechselnd aufgerufen werden,
		ansonsten ungeprüfte Ausnahme */
	public static void meldung(final String text);
		// gibt const text (Leseparameter) im Fenster aus, wartet auf Bestätigung
	public static void start(); // zum Überschreiben
	... // weitere Teile der Spezifikation
}
public class ZweiEimer { // zwei statische animierte Datenbehälter
	public static void fuellenLinks() throws VollAusn;
		// der erste Eimer wird gefüllt; wenn schon gefüllt, geprüfte Ausnahme
		... // fuellenRechts, entleerenLinks, entleerenRechts ähnlich
	public static void anzeigenLinks(); // der erste Eimer wird im Fenster angezeigt
	public static void anzeigenRechts(); // der zweite Eimer im Fenster angezeigt
}
public class ToleranterEimer { // animierter fehlertoleranter Datenbehälter
	public static void anzeigen(); // der Eimer wird im Fenster angezeigt
	public static void fuellen(); // der Eimer wird gefüllt; wenn gefüllt, Fehlermeldung
	public static void entleeren(); // der Eimer wird entleert; wenn leer, Fehlermeldung
}
public class Eimer { // Klasse zum Ausprägen; animierter dynamischer Datenbehälter
	public void fuellen() throws VollAusn;
		// Eimer wird gefüllt, wenn leer; sonst geprüfte Ausnahme
	public void entleeren() throws LeerAusn;
		// Eimer wird entleert, wenn voll; sonst geprüfte Ausnahme
	/** Reihenfolgebedingungen wie bei EinEimer */
	public void anzeigen(); // Eimer wird im Fenster angezeigt und animiert
	public void meldung(final String text); // const text im Fenster
	... // weitere Teile der Spezifikation
}
... // weitere Klassen

3.2.2. Implementierung eines Pakets

Seite 61

package paket;

Abb. 3.2: Zugriffsschutz-Ebenen

Übung 3.5: In der Übung 3.1 auf Seite 52 haben Sie die Klasse SechsmalFuellen aus dem Programm (3.3) auf Seite 51 erweitert. Der Compiler hat Ihre Klasse nur dann übersetzen können, wenn die Klassen FuenfmalFuellen und SechsmalFuellen im selben Verzeichnis (d.h. im anonymen Paket) lagen.

Lösen Sie nun dieselbe Aufgabe, indem Sie die beiden Klassen FuenfmalFuellen und SechsmalFuellen in ein eigenes Paket legen. Hierzu müssen Sie in den beiden Klassen ein package-Klausel vereinbaren und die beiden Klassen in ein Unterverzeichnis (mit demselben Namen) Ihres Arbeitsverzeichnisses verlagern. Ihre neue Klasse (die SechsmalFuellen erweitert) soll nach wie vor im anonymen Paket liegen.

3.3. Schachtelungen

Seite 62

3.3.1. Sichtbarkeit und Lebensdauer

Seite 63

public class Sichtbarkeit { // (3.11)
	private Eimer globalerEimer = new Eimer();
	... // Definition weiterer globalen Referenzen (evtl. mit Objekten)
	private void prozedur() { // ¬
		Eimer lokalerEimer = new Eimer();
		... // Definition der lokalen Referenzen von prozedur
		... // Benutzung der Referenzen aus der Klasse Sichtbarkeit
			// und aus prozedur (nicht aus start)
	}
	public void start() {
		Eimer weitererEimer = new Eimer();
		... // Definition der lokalen Referenzen von start
		... // Benutzung der Referenzen aus Sichtbarkeit und start
		// ein Zugriff auf die Referenzen von prozedur ist nicht möglich
		prozedur(); // Aufruf (mit lokalerEimer) ¬
			...
		prozedur(); // ein weiterer Aufruf (mit einem anderen lokalerEimer)
	}
}

Abb. 3.3: Statische und dynamische Struktur

Lebensdauer Ê Sichtbarkeit.

Sichtbarkeit Kennzeichnung Erreichbarkeit Lebensdauer
lokal innerhalb einer Methode aus der Methode Methode läuft
privat private aus der Klasse Objekt erreichbar
geschützt protected aus Unterklassen

"

paketweit innerhalb einer Klasse aus Klassen des Pakets

"

öffentlich public aus Kundenklassen

"

Abb. 3.4: Sichtbarkeitstufen in Java

3.3.2. Statische Schachtelung von Klassen

Seite 65

public class AuesserKlasse { // (3.12)
	private Eimer globalerEimer = new Eimer();
	private Eimer andererEimer = new Eimer();
	private class InnereKlasse1 { // ¬
		private Eimer lokalerEimer;
		private void prozedur(Eimer parameter) {
			lokalerEimer.kopieren(parameter);
			globalerEimer.kopieren(lokalerEimer); // von innen erreichbar
		}
	}
	class InnereKlasse2 { // ¬
		Eimer lokalerEimer; // ein ganz anderes Objekt als zuvor
		Eimer andererEimer; // gleicher Name, jedoch neues Objekt
		private void prozedur(Eimer parameter) {
			lokalerEimer.kopieren(andererEimer); // nicht das lokale
			InnereKlasse1 referenz = new InnereKlasse1();
			referenz.prozedur(globalerEimer); // Aufruf mit Objekt möglich ¬
			// InnereKlasse1.prozedur(globalerEimer); // nicht zulässig
		}
	}
	public void start() {
		InnereKlasse1 referenz = new InnereKlasse1();
		referenz.prozedur(globalerEimer); // Aufruf nur mit Objekt möglich ¬
	}
}

Abb. 3.5: Statische Schachtelung

3.3.3. Blöcke

Seite 66

{ // Anfang eines Methodenrumpfs // (3.13)
	Eimer eimer1 = new Eimer(); // und andere Vereinbarungen
	... // Anweisungen
	{ // innerer Block ¬
		Eimer eimer2 = new Eimer(); // lebt nur im inneren Block
		... // hier sind weitere geschachtelte Blöcke möglich
	} // Ende des inneren Blockes
	... // und weitere Anweisungen im Rumpf
	eimer2.fuellen(); // der Compiler meldet Fehler: eimer2 nicht sichtbar ¬
} // Ende des Rumpfes

3.3.4. Geschützte Blöcke

Seite 67

void tolerant() { // (3.14)
	try { // Anfang des Prozedurrumpfs
		aufruf1(); // kann Ausnahme auslösen
	}
	catch (Ausnahme ausnahme) { ... }
	// aufruf2 wird ausgeführt, selbst wenn Ausnahme ausgelöst wurde:
	aufruf2(); // wenn Ausnahme auslösen kann, muss in tolerant spezifiziert werden
		// wenn Ausnahme auftritt, wird die Ausführung abgebrochen, da nicht geschützt
	try { // weiterer geschützter Block ¬
		aufruf3(); // kann Ausnahme auslösen
		try { // innerer Block ¬
			aufruf4(); // kann Ausnahme auslösen
		}
		catch (Ausnahme ausnahme) { ... } // Ausnahmebehandlung des inneren Blockes ¬
		aufruf5(); // läuft weiter, selbst wenn aufruf4 Ausnahme ausgelöst hat
	} // Ende des inneren Blocks
	catch (Ausnahme ausnahme) { ... } // Ausnahmebehandlung des äußeren Blockes ¬
	aufruf6(); // wenn Ausnahme auslöst, wird weitergereicht; muss spezifiziert werden
	// Anweisungen des äußeren Blocks werden auch im Ausnahmefall ausgeführt
} // Ende des Prozedurrumpfs

Übung 3.6: In der Übung 2.13 auf Seite 39 haben Sie gezeigt, dass die Klasse ToleranterEimer im Fehlerfall keine Ausnahmen auslöst; der Fehler wurde innerhalb der Methoden abgefangen. Ein Fehler im Programm (2.26) auf Seite 41 hat eine Ausnahme ausgelöst. Sie konnte zwar aufgefangen werden, die Ausführung des Programms wurde aber unterbrochen. Lösen Sie nun das Problem, damit das Programm nach einem Fehler weiter läuft, auf zweierlei Weise: mit einer Prozedur und mit geschützten Blöcken.

Schreiben Sie nun eine Prozedur, in der Sie eine Ausnahme auslösen (z.B. einen vollen Eimer füllen). Rufen Sie diese Prozedur zweimal auf. Geben Sie anschließend eine Meldung aus, dass das Programm beendet wurde. Sie müssen natürlich die Ausnahme (z.B. VollAusn) in der start-Methode auffangen. Der zweite Aufruf wird nicht mehr ausgeführt. Auch die Meldung kommt nicht, da start mit einer Ausnahme unterbrochen wurde.

In einer zweiten Version fangen Sie die Ausnahme in der Prozedur auf (und geben Sie eine Fehlermeldung aus). Jetzt werden beide Prozeduraufrufe ausgeführt. Auch die Meldung kommt zum Schluss.

Drittens gestalten Sie Ihr Programm so um, dass die beiden Prozeduraufrufe in je einem geschützten Block stattfinden. Auch jetzt kommt Ihre Schlussmeldung.

3.3.5. Der Stapel

Seite 68

Abb. 3.6: Dynamische Schachtelung

private static void prozedur() { // (3.15)
	... // lokale Referenzen vereinbart
return; // die lokalen Referenzen (u.U. ihre Objekte) werden vernichtet ¬
	... // die Anweisungen werden nicht mehr ausgeführt
}

3.3.6. Rekursive Aufrufe

Seite 69

public static void rekursivDirekt() { // (3.16)
	... rekursivDirekt(); ...
}
public static void rekursivIndirekt1() {
	... rekursivIndirekt2(); ... // ¬
}
public static void rekursivIndirekt2() {
	... rekursivIndirekt1(); ... // ¬
}

3.3.7. Abbruch einer Rekursion

Seite 70

public static void rekursiv() { // (3.17)
	try {
		...
		aufruf(); // throws Ausnahme (wenn die Rekursion unterbrochen werden soll)
		...
		rekursiv(); // ¬
		...
	}
	catch (Ausnahme ausnahme) {} // der letzte Aufruf soll "normal" zu Ende gehen
}
import lehrbuch.*; // (3.18)
public class EimerRekursiv extends java.applet.Applet {
	private void eimerRekursiv() {
		try {
			Eimer eimer = new Eimer();
			eimer.anzeigen(); // throws Eimer.MehrAlsVierFehler nach 4. Aufruf
			eimerRekursiv(); // rekursiver Aufruf ¬
			eimer.fuellen();
		} catch (Eimer.MehrAlsVierFehler ausnahme) {} // letzter Aufruf ¬
		...

Abb. 3.7: Stapeln von rekursiven Aufrufen

Übung 3.7: Gestalten Sie das Programm (3.18) auf Seite 70 so um, dass Sie den Eimer vor dem rekursiven Aufruf füllen und danach entleeren. Beobachten und erklären Sie die Reihenfolge, wie die vier Eimer gefüllt und entleert werden.

3.3.8. Wirkungsmechanismus der Ausnahmen

Seite 71

3.4. Verwendung von Standardpaketen

Seite 72

3.4.1. Geschachtelte Pakete

Seite 72

import paket.unterpaket.Klasse;
import paket.unterpaket.*;
package paket.unterpaket;
public class ...

3.4.2. Standardpakete

Seite 73

public class AuchNichtLeer extends java.applet.Applet {} // (3.19)

3.4.3. Das Standard-Hallo-Applet

Seite 73

public class GrussAusBayern extends java.applet.Applet { // (3.20)
	public void paint(java.awt.Graphics grafik) {
		grafik.drawString("Grüß Gott!", 50, 25); // ¬
	}
}
protected void drawString(String, int, int);

3.4.4. Das Standard-Hallo-Anwendung

Seite 74

public class GrussAusBerlin { // (3.21)
	public static void main(String[] args) {
		java.lang.System.out.println("Tag!"); // java.lang kann weggelassen werden ¬
	}
}
java .	lang .		System .	out .		println (	"Tag!"	);
Paket	Unterpaket	Klasse		Referenz	Methode	Parameter
java GrussAusBerlin
public class EimerHauptprogramm { // (3.22)
	public static void main(String[] args) {
		try {
			lehrbuch.Eimer eimer = new lehrbuch.Eimer();
			eimer.anzeigen();
			eimer.fuellen();
		} catch (lehrbuch.VollAusn ausnahme) {}
	}
}

Übung 3.8: Entwickeln Sie eine Anwendung sowie ein Applet mit Hilfe von Standardpaketen, die drei Ihrer Lieblingssprüche (mit jeweils einer Leerzeile dazwischen) auf dem Bildschirm sichtbar macht. Experimentieren Sie beim Applet mit verschiedenen Positionen im Fenster und erklären Sie den Zusammenhang zum Applet-Klausel in der HTML-Datei.

Übung 3.9: Gestalten Sie Ihre Lösung der Übung 3.3 auf Seite 56 zu einem Hauptprogramm um. Denken Sie daran, dass aus der static-Methode main heraus nur static-Methoden aufgerufen und static-Referenzen angesprochen werden können.

3.4.5. Implizite Erweiterung

Seite 75

3.4.6. Impliziter Import

Seite 76

import java.lang.*;

Klassen und Objekte Inhaltsverzeichnis Hypertext-Version Implementierung von Datenbehältern © APSIS GmbH