Strukturierung von Klassen Inhaltsverzeichnis Hypertext-Version Werte © APSIS GmbH

4. Implementierung von Datenbehältern

Seite 77

4.1. Klassenkomponenten

Seite 77

import lehrbuch.*; // (4.1)
public class ZweiEimer {
	private static Eimer linkerEimer = new Eimer(), rechterEimer = new Eimer(); // ¬
		// zwei Klassenkomponenten der Klasse Eimer
	public static void fuellenLinks() throws VollAusn {
		linkerEimer.fuellen(); // Methode fuellen aus der Klasse Eimer ¬
	}
	... // die weiteren Operationen ähnlich: Die Leistung von Eimer wird benutzt
}
public class Klasse { // (4.2)
	private void methode() { ... } // nicht static
	public static void main(String[] args) {
		Klasse objekt = new Klasse(); // umhüllende Klasse
		objekt.methode(); // aufrufbar
	}
}

Übung 4.1: Implementieren Sie den Datenbehälter mit der Spezifikation in der Datei ToleranterEimer.spec mit Hilfe der Klasse lehrbuch.Eimer. Wie schon angemerkt, ist sie fehlertolerant, d.h. die von lehrbuch.Eimer ausgelösten Ausnahmen müssen innerhalb den Methodenrümpfen von ToleranterEimer aufgefangen werden. Als Maßnahme im Fehlerfall können Sie die Methode meldung mit einem Zeichenkettenparameter aus der Klasse lehrbuch.Eimer aufrufen: Diese gibt eine entsprechende Fehlermeldung im Fenster aus.

Testen Sie Ihre Klasse mit Hilfe des Programms (2.26) auf Seite 41.

Übung 4.2: Implementieren Sie das statische Objekt EinDoppelEimer mit Hilfe von lehrbuch.Eimer. Die Methoden wie fuellen und entleeren sollen jeweils zwei Eimer bedienen. Selbstverständlich entwickeln Sie dazu auch ein Testprogramm (main), in dem Sie die Operationen des Objekts aufrufen, um ihre Wirkung zu überprüfen.

Die Spezifikation der Klasse EinDoppelEimer ist:

public class EinDoppelEimer { // (4.3)
	// stellt zwei leere Datenbehälter zur Verfügung, die zusammen bedient werden
	public static void fuellen() throws VollAusn; // beide Eimer werden gefüllt
	public static void entleeren() throws LeerAusn; // beide werden entleert
	public static void anzeigen(); // beide Eimer werden im Fenster angezeigt
}

4.2. Implementierung von Ausnahmen

Seite 79

4.2.1. Verbreitung von Ausnahmen

Seite 79

Abb. 4.1: Verbreitung einer Ausnahme

4.2.2. Auslösen von Ausnahmen

Seite 79

public class LeerFehler extends Error {} // (4.4)
public class VollFehler extends Error {}
// die Vereinbarungen der Ausnahmeklassen stehen in extra Dateien, weil sie public sind
import lehrbuch.*;
public class EinEimer extends java.applet.Applet {
	private static Eimer eimer; // Klassenkomponente, eine Referenz ¬
	public static void fuellen() throws VollFehler {
		try {
			eimer.fuellen(); // throws VollAusn
		}
		catch (VollAusn ausnahme) {
			throw new VollFehler(); // hier wird die neue Ausnahme ausgelöst ¬
		}
	}
	... // die weiteren Operationen ähnlich: Die Leistung von Eimer wird benutzt;
		// Ausnahmen werden aufgefangen und neue ausgelöst

4.2.3. Klasseninitialisator

Seite 80

static { // Klasseninitialisierung ¬
	// wird alleine durchs Erweitern oder Ausprägen, ohne Operationsaufruf ausgeführt:
	eimer = new Eimer();
	eimer.anzeigen(); // im Fenster wird ein leerer Eimer angezeigt
}

Übung 4.3: In der Übung 4.2 auf Seite 78 haben Sie die Ausnahmen von der Klasse Eimer übernommen und einfach weitergereicht. Im Testprogramm mussten Sie diese auffangen. Fertigen Sie nun eine Version Ihrer Klasse EinDoppelEimer an, die ungeprüfte Ausnahmen auslöst. Zeigen Sie, dass Sie im Testprogramm die Freiheit haben, diese aufzufangen oder nicht.

4.3. Objektkomponenten

Seite 81

4.3.1. Implementierung von dynamischen Datenbehältern

Seite 81

import lehrbuch.*; // (4.5)
public class DoppelEimer {
	private Eimer linkerEimer = new Eimer(), rechterEimer = new Eimer(); // ¬
	public void fuellen() throws VollAusn {
		linkerEimer.fuellen();
		rechterEimer.fuellen();
	}
	... // die weiteren Operationen entleeren und anzeigen ähnlich:
	// die beiden Objekte werden zusammen bedient
}

Abb. 4.2: Gekapselte Referenzen

Übung 4.4: Entwickeln Sie die Klasse DoppelEimer fertig und testen Sie sie mit zwei Objekten: Beobachten Sie die Wirkung der Operationen. Lösen Sie auch eine Ausnahme aus und erklären Sie, wie sie sich verbreitet.

4.3.2. Klassen- und Objektkomponenten

Seite 82

import lehrbuch.*; // (4.6)
public class MischEimer {
	private Eimer eigenerEimer = new Eimer(); // Objektkomponente ¬
	private static Eimer gemeinsamerEimer = new Eimer();// Klassenkomponente ¬
	public void fuellen() {
		try {
			eigenerEimer.fuellen();
			gemeinsamerEimer.fuellen();
		} catch (VollAusn ausnahme) {}
	}
	... // weiter ähnlich
}
MischEimer e1 = new MischEimer(), e2 = new MischEimer();
e1.fuellen(); // zwei Eimer werden gefüllt
e2.fuellen(); // der dritte Eimer wird gefüllt; Ausnahme wird ignoriert

Abb. 4.3: Objekt- und Klassenkomponenten

Übung 4.5: Entwickeln Sie die Klasse MischEimer fertig und testen Sie sie mit drei Objekten: Beobachten Sie die Wirkung der Operationen und erklären Sie den Ablauf Ihres Programms.

4.3.3. Ledige Objekte

Seite 83

LedigeKlasse.methode();

4.4. Schnittstellen

Seite 83

Abb. 4.4: Wiederverwendbare Klassen

4.4.1. Kopieren

Seite 84

public class GarantierterEimerKop { // (4.7)
	public void fuellen() throws VollAusn; // Eimer wird gefüllt, wenn leer
		... // usw., wie die Spezifikation von Eimer, zusätzlich aber:
	public void garantiertFuellen(); // Eimer wird auf jeden Fall gefüllt ¬
	public void garantiertEntleeren(); // Eimer wird auf jeden Fall entleert
}

4.4.2. Einkaufen

Seite 85

import lehrbuch.*; // (4.8)
public class GarantierterEimerEink {
	protected Eimer eimer = new Eimer();
	public void fuellen() throws VollAusn {
		eimer.fuellen(); // ausgelöste Ausnahme wird weitergereicht ¬
	}
		... // die weiteren Methoden aus der Klasse Eimer ähnlich
	public void garantiertFuellen() { // neuer Mutator, löst keine Ausnahme aus ¬
		try {
			eimer.fuellen();
		}
		catch (VollAusn ausnahme) {} // wenn Eimer gefüllt, passiert nichts
	}
	public void garantiertEntleeren() { ... // ähnlich

Übung 4.6: Erweitern Sie durch Einkauf die Schnittstelle der Klasse DoppelEimer aus der Übung 4.4 (auf Seite 82) um die zwei Methoden doppeltFuellen und doppeltEntleeren. Diese sollen die zwei parallelen Eimer zweimal nacheinander füllen bzw. entleeren (und dazwischen entleeren bzw. füllen). Sie sollen keine Ausnahmen auslösen, sondern gegebenenfalls das erste Füllen bzw. Entleeren weglassen. Vergessen Sie nicht ein geeignetes Testprogramm (am besten ein Hauptprogramm) dazu zu schreiben.

4.4.3. Erben

Seite 85

public class GarantierterEimerErb extends lehrbuch.Eimer { // (4.9)
	// alle Komponenten werden von Eimer geerbt; zusätzliche Methoden:
	public void garantiertFuellen() { // Eimer wird auf jeden Fall gefüllt
		try {
			super.fuellen(); // ¬
		}
		catch (lehrbuch.VollAusn ausnahme) {} // wenn Eimer gefüllt, passiert nichts
	}
	public void garantiertEntleeren() { // Eimer wird auf jeden Fall entleert
		... // ähnlich
public class Garantie { // (4.10)
	public static void main(String[] args) {
		GarantierterEimerErb eimer = new GarantierterEimerErb(); // ¬
			// austauschbar auf GarantierterEimerEink
		try {
			eimer.fuellen(); // aus der Oberklasse
			eimer.garantiertFuellen(); // aus der Unterklasse
			...

Übung 4.7: Lösen Sie die Übung 4.6 auf Seite 85 durch Erweiterung. Ihr Testprogramm sollte unverändert ablaufen.

4.4.4. Implementierung von Schnittstellen

Seite 86

import lehrbuch.*; // VollAusn, LeerAusn // (4.11)
interface GarantierterEimer {
	public void anzeigen(); // Eimer wird angezeigt
	public void fuellen() throws VollAusn; // Eimer wird gefüllt, wenn leer
	public void entleeren() throws LeerAusn; // Eimer wird entleert, wenn leer
	public void garantiertFuellen(); // Eimer wird auf jeden Fall gefüllt ¬
	public void garantiertEntleeren(); // Eimer wird auf jeden Fall entleert
}
public class GarantierterEimerEink implements GarantierterEimer {
		... // wie im Programm (4.8)
public class GarantierterEimerErb implements GarantierterEimer extends lehrbuch.Eimer {
		... // wie im Programm (4.9)

Übung 4.8: Implementieren Sie die folgende Schnittstelle mit Hilfe des Pakets lehrbuch.Kreis:

interface NeunKreise { // (4.12)
	public void zeichnen(); // neun Kreise verschiedener Farben erscheinen
	public void faerben(); // die Kreise erhalten neue unterschiedliche Farben
}

Erstellen Sie – selbstverständlich – auch ein Testprogramm dazu.

4.5. Konstruktoren und Destruktoren

Seite 87

4.5.1. Konstruktoren

Seite 87

public SichtbarerEimer() {
	super(); // Konstruktor der Oberklasse ¬
	anzeigen();
}
public class SichtbarerEimer extends lehrbuch.Eimer { // (4.13)
	public SichtbarerEimer() { ... // parameterloser Konstruktor wie oben
	public SichtbarerEimer(final String zeichenkette) { // parametrisierter Konstruktor
		this(); // parameterloser Konstruktor derselben Klasse ¬
		try {
			fuellen();
		}
		catch (lehrbuch.VollAusn ausnahme) { System.err.println(ausnahme); }
			// dürfte nicht geschehen können
	}
}
public class GarantierterEimerEink {
	protected Eimer eimer;
	public GarantierterEimerEink() {
		eimer = new Eimer(); // ¬
	}
public class Konstruktor { // (4.14)
	public static void main(String[] args) {
		try {
			SichtbarerEimer e1 = new SichtbarerEimer(); // leerer sichtbarer Eimer erzeugt
			SichtbarerEimer e2 = new SichtbarerEimer("Hallo"); // voller sichtbarer Eimer ¬
			e1.fuellen(); // Eimer-Methoden können aufgerufen werden
			...
SichtbarerEimer e3 = new SichtbarerEimer(""); // leere Zeichenkette: aktueller Parameter
public void Eimer(final Eimer eimer); // const eimer

Übung 4.9: Erweitern Sie die Klasse lehrbuch.Eimer um einen Konstruktor, der den erzeugten Eimer anzeigt und seine geschützte Methode fuellenMitWein aufruft. Schreiben Sie auch ein Testprogramm, in dem Sie drei Eimer unterschiedlicher Klassen anlegen und Operationen ausführen.

4.5.2. Destruktoren

Seite 89

protected void finalize();
super.finalize();

4.6. Typkompatibilität

Seite 89

4.6.1. Aufwärtskompatibilität

Seite 89

class Oberklasse { // (4.15)
	public void methode() { ... }
	...
}
class Unterklasse extends Oberklasse ...
class Programm {
	public static void main(String[] args) {
		Unterklasse objekt = new Unterklasse();
		objekt.methode(); // methode aus Oberklasse für objekt der Unterklasse ¬
		...
		prozedur(objekt); // objekt der Unterklasse für parameter der Oberklasse ¬
	}
	public static void prozedur(Oberklasse parameter) { ... }
Unterklasse unterReferenz = new Unterklasse();
Oberklasse oberReferenz; // Referenz der Oberklasse
oberReferenz = unterReferenz; // Referenz der Oberklasse, Objekt der Unterklasse ¬
import lehrbuch.*; // Eimer, VollAusn, LeerAusn // (4.16)
public class Aufwaertskompatibel extends java.applet.Applet {
	public void fuellenUndEntleeren(Eimer eimer) throws VollAusn, LeerAusn {
		eimer.fuellen();
		eimer.entleeren();
	}
	public void start() {
		try {
			GarantierterEimerErb garantierterEimer = new GarantierterEimerErb();
				// aus dem Programm (4.9)
			garantierterEimer.anzeigen(); // ¬
				// Methode der Oberklasse mit Zielobjekt der Unterklasse
			fuellenUndEntleeren(garantierterEimer); // ¬
				// Objekt der Unterklasse eingesetzt für Parameter der Oberklasse
			Eimer eimer = garantierterEimer; // ¬
				// Referenz der Unterklasse speichert Adresse der Oberklasse
			...

Abb. 4.5: Aufwärtskompatibilität

Übung 4.10: In der Übung 4.9 auf Seite 89 haben Sie die Klasse lehrbuch.Eimer erweitert. Zeigen Sie in einem Programm, dass Objekte dieser Klasse auch von Referenzen der Oberklasse referiert werden und als Parameter der Oberklasse übergeben werden können.

4.6.2. Erzwungene Abwärtskompatibilität

Seite 91

public static void prozedur(Unterklasse parameter) { ... } // (4.17)
public static void main(String[] args) {
		Oberklasse objekt = new Oberklasse();
		objekt.methode(); // Typfehler ¬
		prozedur(objekt); // Typfehler ¬
		Unterklasse referenz = objekt; // Typfehler ¬
		...
Unterklasse unterReferenz = new Unterklasse();
Oberklasse oberReferenz; // Referenz der Oberklasse
oberReferenz = unterReferenz; // aufwärtskompatibel
oberReferenz.methode(); // Typfehler, obwohl es möglich sein würde ¬
(Unterklasse)oberReferenz.methode(); // Typkonvertierung
import lehrbuch.*; // Eimer, VollAusn, LeerAusn // (4.18)
public class Abwaertskompatibel {
	public static void fuellenUndEntleeren(GarantierterEimerErb eimer)
			throws VollAusn, LeerAusn {
		eimer.fuellen();
		eimer.entleeren();
	}
	public static void main(String[] args) {
		try {
			GarantierterEimerErb garantierterEimer = new GarantierterEimerErb();
			garantierterEimer.anzeigen();
			Eimer eimer = garantierterEimer; // aufwärtskompatibel ¬
			fuellenUndEntleeren(eimer); // Typfehler
			fuellenUndEntleeren((GarantierterEimerErb)eimer); // ¬
				// erzwungene Abwärtskompatibilität
			...

Abb. 4.6: Erzwungene Abwärtskompatibilität

Kompatibilität

aufwärts (implizit)

Abwärts, erzwungen (explizit)

Referenzzuweisung oberRef = unterRef; UnterRef = (UnterKl)oberRef;
Methodenaufruf unterRef.oberMethode(); ((UnterKl)oberRef).unterMethode();
Parameterübergabe void prozedur(OberKl parameter)

prozedur(unterRef);

void prozedur(UnterKl parameter)

prozedur((UnterKl)oberRef)

Abb. 4.7: Drei Fälle der Typkompatibilität

Übung 4.11: In der Übung 4.10 auf Seite 91 haben Sie die implizite Aufwärtskompatibilität nachgewiesen. Zeigen Sie jetzt in einem Programm, dass es umgekehrt nicht geht: Objekte der Oberklasse können von keinen Referenzen der Unterklasse referiert werden. Rufen Sie eine Methode der Unterklasse für eine Referenz der Oberklasse mit expliziter Typkonvertierung auf. Untersuchen Sie, wie der Interpreter reagiert, wenn Sie absichtlich einen Fehler machen und ein Objekt der Oberklasse als Parameter der Unterklasse übergeben. Fangen Sie die erhaltene Ausnahme java.lang.ClassCastException in einem geschützten Block auf.

Verfolgen Sie den Ablauf Ihres Programms. Erleichtern Sie die Verfolgung mit Hilfe von Ausgaben mit System.out.println.

4.6.3. Typschwäche

Seite 93

Eimer eimer = new Eimer(); // (4.19)
Kreis kreis = new Kreis();
Object objekt = eimer; // aufwärtskompatibel
eimer = (Eimer)objekt; // abwärtskompatibel
((Eimer)objekt).fuellen(); // abwärtskompatibel ¬
kreis = (Kreis)objekt; // throws ClassCastException; kein Fehler vom Compiler
((Kreis)objekt).bemalen(); // throws ClassCastException; kein Fehler vom Compiler ¬

4.6.4. Polymorphie

Seite 93

class Oberklasse { // (4.20)
	public void methode() { System.out.println("Version 0"); }
}
class ErsteUnterklasse extends Oberklasse {
	public void methode() { System.out.println("Version 1"); } // überschrieben
}
class ZweiteUnterklasse extends Oberklasse {
	public void methode() { System.out.println("Version 2"); } // überschrieben
}
public class Polymorph {
	public static void prozedur(Oberklasse parameter) {
		parameter.methode(); // welche Version? ¬
	}
	public static void main(String[] args) {
		Oberklasse objektOber = new Oberklasse();
		ErsteUnterklasse objektErste = new ErsteUnterklasse();
		ZweiteUnterklasse objektZweite = new ZweiteUnterklasse();
		objektOber.methode(); // Version 0 // keine Polymorphie
		objektErste.methode(); // Version 1
		objektZweite.methode(); // Version 2
		Oberklasse referenz; // Polymorphie über Zielobjekt:
		referenz = objektErste; // aufwärtskompatibel
		referenz.methode(); // Version 1, nicht Version 0! ¬
		referenz = objektZweite; // aufwärtskompatibel
		referenz.methode(); // Version 2, nicht Version 0! ¬
		prozedur(objektOber); // ruft Version 0 auf
		// Polymorphie über Parameter:
		referenz = objektErste; // aufwärtskompatibel
		prozedur(referenz); // ruft Version 1 auf, nicht Version 0! ¬
		referenz = objektZweite; // aufwärtskompatibel
		referenz.methode(); // ruft Version 2 auf, nicht Version 0! ¬
	}
}
interface PolymorpherEimer { // (4.21)
	public void fuellen() throws lehrbuch.VollAusn;
	public void entleeren() throws lehrbuch.LeerAusn;
}
class WasserEimer extends lehrbuch.Eimer implements PolymorpherEimer { // ¬
	public WasserEimer() { super(); anzeigen(); }
} // alle Methoden – auch fuellen – werden geerbt
class WeinEimer extends lehrbuch.Eimer implements PolymorpherEimer { // ¬
	public WeinEimer() { super(); anzeigen(); }
	public void fuellen() throws lehrbuch.VollAusn { // fuellen wird überschrieben
		super.fuellenMitWein(); // geschützte Methode aus der Klasse Eimer
	}
}
public class ZweiPolymorpheEimer { // (4.22)
	public static void main(String[] args) {
		try {
			PolymorpherEimer eimer; // Referenz vom Schnittstellentyp ¬
			WasserEimer wasserEimer = new WasserEimer();
			WeinEimer weinEimer = new WeinEimer();
			eimer = wasserEimer;
			eimer.fuellen(); // erste Version ¬
			eimer.entleeren(); // es gibt nur eine Version
			eimer = weinEimer;
			eimer.fuellen(); // zweite Version ¬
			eimer.entleeren();
			prozedur(wasserEimer); // erste Version
			prozedur(weinEimer); // zweite Version
		} catch (Exception ausnahme) {} // ¬
	}
	private static void prozedur(PolymorpherEimer e) throws lehrbuch.VollAusn {
		e.fuellen(); // welche Version? Hängt vom aktuellen Parameter ab ¬
	}
}

Übung 4.12: Demonstrieren Sie Polymorphie mit Kreisen. Entwickeln Sie drei Unterklassen von lehrbuch.Kreis, in denen Sie die Methode bemalen überschreiben. Hierin melden Sie (z.B. mit der static-Methode Kreis.meldung) vor dem Aufruf der geerbten Methode, welche Version gerade ausgeführt wird. Speichern Sie in einer Referenz der Oberklasse Kreis Adressen von vier Kreisobjekten unterschiedlicher Klassen nacheinander und rufen Sie für sie die überschriebene Methode bemalen auf. Verfolgen Sie den Ablauf und erklären Sie, wann welche Version ausgeführt wird.

Schreiben Sie auch eine Prozedur mit einem Parameter der Klasse Kreis. Rufen Sie hierin die Methode bemalen auf. Rufen Sie Ihre Prozedur viermal auf und übergeben Sie als Parameter Ihre vier Kreisobjekte unterschiedlicher Klassen. Verfolgen Sie den Ablauf und erklären Sie, wann welche Version der Methode bemalen ausgeführt wird.


Strukturierung von Klassen Inhaltsverzeichnis Hypertext-Version Werte © APSIS GmbH