Multibehälter Inhaltsverzeichnis Hypertext-Version Steuerstrukturen © APSIS GmbH

9. Implementierung von Multibehältern

Seite 195

9.1. Verbunde

Seite 195

class Verbund { // (9.1)
	Element1 element1 = new Element1();
	Element2 element2 = new Element2();
		... // usw. entsprechend der Anzahl der Elemente
	ElementN elementN = new ElementN();
}
Verbund objekt1 = new Verbund(), objekt2 = new Verbund();
objekt1.element1.methode();
	// methode der Klasse Element1 wird für das Objekt objekt1.element1 aufgerufen
objekt2.element1.methode();
	// methode der Klasse Element1 wird für das Objekt objekt2.element1 aufgerufen
objekt2.element2.methode();
 // (eine andere) methode der Klasse Element2 wird für objekt2.element1 aufgerufen
objekt1.getElement1().methode(); // getter
objekt1.setElement1(wert); // setter
public class Kunde { // (9.2)
	public String name;
	public int bonitaet;
		...
}
...
Kunde kunde = new Kunde();
kunde.name = new String("Solymosi"); // Direktzugriff
kunde.bonitaet = 100000;

9.2. Reihungen

Seite 196

9.2.1. Reihungsobjekte

Seite 196

Element[] reihung; // (9.3)
char[] zeile;
zeile = new char[80];
Eimer[] zweiEimer = new Eimer[2];
zweiEimer[1] = new Eimer();
zweiEimer[2] = new Eimer();
public void linksFuellen() throws VollAusn {
	zweiEimer[1].fuellen();
}
public void rechtsFuellen() throws VollAusn {
	zweiEimer[2].fuellen();
}
Element[] referenz = new Element[LAENGE];
final int LAENGE = 20;
public void Reihung(int laenge) {
	reihung = new Element(laenge);
}

Übung 9.1: Entwickeln Sie ein der Übung 6.8 auf Seite 133 ähnliches Programm für neun Kreise mit einer Reihung. An Stelle einer Referenz auf den vorgewählten Kreis können sie einen int-Wert mit der Nummer eines der neun Kreise einlesen. Dieser Index der Reihung "zeigt" auf den ausgewählten Kreis ähnlich wie eine Referenz. Sie können anschließend die gewünschte Aktion ("Bemalen", usw.) auswählen. Achten Sie jedoch darauf, dass die Kreise in derselben Reihenfolge angezeigt werden, wie die Operation zeichnen für sie aufgerufen wurde. Damit diese Ihrer Nummerierung entspricht, ist es zweckmäßig, vor dem Aufruf des Menüs alle neun Kreise zu zeichnen und anschließend zu verstecken; dann behalten sie ihre Positionen im Fenster während des gesamten Programmlaufs.

9.2.2. Reihungsklassen

Seite 197

Element[] reihung = new Element[LAENGE];
int index = reihung.length – 1; // ¬
reihung[index] = wert; // das letzte Element der Reihung
try {
	reihung[reihung.length] = wert; // falscher Zugriff ¬
} catch (IndexOutOfBoundsException ausnahme) { ...
class Reihungserweiterung extends Element[] { ... // in Java nicht erlaubt
boolean[] logReihung = { true, false, false, true, true };
Eimer[] zweiEimer = { new Eimer(), new Eimer() };

9.2.3. Die Standardklassen für Zeichenketten

Seite 198

String zeichenkette = "Zeichenkette"; // Literal repräsentiert konstantes String-Objekt ¬
int laenge = zeichenkette.length(); // Informator, liefert Anzahl der Zeichen
char zeichen = zeichenkette.charAt(laenge-1); // Informator, liefert ein Zeichen
int pos = zeichenkette.indexOf('e'); // Informator, Index des ersten Vorkommnisses
zeichenkette = "Zweite " + zeichenkette;
zeichenkette += " verlängert"; // Inhalt: "Zweite Zeichenkette verlängert"
zeichenkette = "" + eimer; // Abkürzung für eimer.toString()
zeichenkette = "<" + 0.5 + '>'; // Inhalt ist "<0.5>"
boolean b = true;
String s = String.valueOf(b); // Inhalt ist "true"
s = "" + b; // gleichwertig
Boolean b = new Boolean(zeichenkette);
b = Boolean.valueOf(zeichenkette);
... // für die nummerischen Hüllenklassen Integer, Long, Float und Double ähnlich
int ganzzahl = Integer.parseInt(zeichenkette);
long lang = Long.parseLong(zeichenkette);
StringBuffer puffer = new StringBuffer(); // Konstruktor
char zeichen = (char)System.in.read(); // ein Zeichen von der Konsole lesen
puffer.append(zeichen); // neues Zeichen am Ende des Puffers hinzufügen
	...
puffer.setCharAt(pos, zeichen); // ändert das Zeichen mit der gegebenen Position

9.2.4. Kommandozeilenparameter

Seite 199

Kapitel 3.4.4. auf Seite 74

public static void main(String[] args);
public class Kommandozeilenparameter { // (9.4)
	public static void main(String[] args) {
		System.out.println(args[0]); // ¬
	}
}
java Kommandozeilenparameter
java Kommandozeilenparameter Hallo
Hallo
public class Addieren { // (9.5)
	public static void main(String[] args) {
		int a = new Integer(args[0]).intValue(); // ¬
		int b = new Integer(args[1]).intValue(); // ¬
		int c = a + b
		System.out.println(a + " + " + b + " = " + c);
	}
}

9.2.5. Übersetzungstabellen

Seite 200

import java.awt.Color; // (9.6)
class Farbe extends lehrbuch.Aufz {
	public static final Farbe GRUEN = new Farbe();
	public static final Farbe LILA = new Farbe();
		... // usw.
}
public class Farbuebersetzer {
	private static final Color[] rgb = { Color.green, Color.magenta, ... };
		// in der Reihenfolge, wie die deutschen Farbnamen in Farbe vereinbart wurden
	public static Color farbuebersetzer (final Farbe farbe) {
		return rgb [farbe.pos()]; // ¬
	}
}
getGraphics().setColor(Farbuebersetzer.farbuebersetzer(Farbe.LILA));

Übung 9.2: Schreiben Sie ein Java-Applet, in dem – ähnlich wie im Programm (3.20) auf Seite 74 – eine Schriftzeile ausgegeben wird, deren Farbe aber zuvor von einer Auswahlliste ausgewählt wird.

9.2.6. Mehrdimensionale Reihungen

Seite 201

Element[][] tabelle = new Element[BREITE][HOEHE]; // (9.7)
int[][] matrix = {
	{2, -6, 3},
	{12, 0, 6},
	{1, -1, 1},
};

9.2.7. Implementierung von Multibehältern als Reihung

Seite 201

public class StapelPol implements lehrbuch.kapitel8.Stapel { // (9.8)
		// polymorphe Implementierung von Stapel
	private Object[] inhalt; // ¬
	private int spitze;
	public StapelPol(int groesse) {
		inhalt = new Object[groesse];
		entleeren();
	}
	public void entleeren() { // ensures leer();
		spitze = -1;
	}
	public void eintragen(final Object element) throws VollAusn {
		try {
			spitze ++; // nächster freier Platz
			inhalt[spitze] = element; // ¬
			// throws IndexOutOfBoundsException, wenn spitze = inhalt.length
		} catch (IndexOutOfBoundsException ausnahme) {
			spitze --; // zurücksetzen
			throw new VollAusn();
		}
	}
	public Object lesen() throws LeerAusn { // requires !leer()
		try {
			return inhalt[spitze]; // letztes eingetragenes Element ¬
				// throws IndexOutOfBoundsException, wenn spitze = -1
		} catch (IndexOutOfBoundsException ausnahme) {
			throw new LeerAusn();
		}
	}
	public void entfernen() throws LeerAusn {
		try {
			attrappe = inhalt[spitze];
			// throws IndexOutOfBoundsException, wenn spitze = -1
			spitze --; // Platz des letzten eingetragenen Elements freigeben
		} catch (IndexOutOfBoundsException ausnahme) {
			throw new LeerAusn();
		}
	}
	private static Object attrappe; // global, damit der Compiler es nicht wegoptimiert
	public boolean leer() { // const
		return spitze == -1;
	}
	public boolean voll() { // const
		return spitze == inhalt.length-1;
	}
}

Übung 9.3: Implementieren Sie die Schnittstelle Warteschlange aus dem Programm (8.17) auf Seite 183 als Reihung (mit integriertem Stapeltestprogramm als main in der Klasse). Um die Ausnahmefälle ohne if erkennen (s. Kapitel 10.1.5. auf Seite 219) zu können, lösen Sie die Ausnahme IndexOutOfBoundsException mit einer Attrappe:

private static Object attrappe; // global, damit der Compiler es nicht wegoptimiert
attrappe = inhalt[anzahl];
	// throws IndexOutOfBoundsException bei anzahl == inhalt.length
attrappe = inhalt[anzahl-1]; // throws IndexOutOfBoundsException bei anzahl == 0

Im Stapel haben wir einen Index spitze geführt. In der Warteschlange müssen Sie zwei Indizes aeltestes und juengstes führen. Wenn ein Index das Ende der Reihung erreicht (überläuft) soll er bei 0 wieder angesetzt werden. Am einfachsten ist dies mit dem Restoperator % zu berechnen:

juengstes = (juengstes + 1) % inhalt.length; // beim Überlauf wird 0

Abb. 9.1: Datenstruktur der Warteschlange

9.3. Verkettete Listen

Seite 203

9.3.1. Rekursiv vereinbarte Klassen

Seite 203

Abb. 9.2: Knoten

class Knoten { // (9.9)
	Object wert; // statt Object beliebige Klasse oder Datentyp (z.B. char) möglich
	Knoten verbindung; // Rekursion ¬
	Knoten(final Object w) { // w like wert; Konstruktor mit einem Parameter
		wert = w; // verbindung == null
	}
	Knoten(final Object w, final Knoten v) { // w like wert
		wert = w;
		verbindung = v;
	}
}
Knoten knoten = new Knoten(Farbe.ROT), andererKnoten = new Knoten(new Eimer());
knoten.wert = Farbe.GRUEN;
andererKnoten.wert = knoten.wert;

9.3.2. Verkettung

Seite 204

Knoten anker = null; // verkettete Liste erreicht über den Verweis auf das letzte Element
// Anfügen eines neuen Knotens:
anker = new Knoten(Farbe.BLAU, anker); // neuer Knoten; der Alte wird verkettet ¬
	...
anker = anker.verbindung; // löschen des letzten Knotens der Liste

Abb. 9.3: Rückwärts verkettete Liste

9.3.3. Rückwärts verkettete Listen

Seite 204

package lehrbuch.kapitel8; // (9.10)
public class StapelListe implements Stapel { // Listenimplementierung von Stapel
	private class Knoten {
		... // innere Klasse wie im Programm (9.9)
	}
	private Knoten anker; // ¬
	public StapelListe() {
		anker = null;
	}
	public void eintragen (final Object element) throws VollAusn {
		try { anker = new Knoten(element, anker); } // new throws OutOfMemoryError
		catch (OutOfMemoryError ausnahme) { throw new VollAusn(); }
	}
	public Object lesen () throws LeerAusn {
		try { return anker.wert; } // . throws NullPointerException
		catch (NullPointerException ausnahme) { throw new LeerAusn(); }	
	}
	public void entfernen() throws LeerAusn {
		try { anker = anker.verbindung; } // . throws NullPointerException
		catch (NullPointerException ausnahme) { throw new LeerAusn(); }	
	}
	public void entleeren() {
		anker = null;
	}
	public boolean leer(){
		return anker == null;
	}
	public boolean voll() {
		try {
			Knoten knoten = new Knoten(); // Versuch
			return false; // ist gelungen
		} catch (OutOfMemoryError ausnahme) {
			return true; // kein Platz mehr
		}
	}
}

Übung 9.4: Testen Sie die obige Klasse mit Ihrem Programm aus der Übung 8.12 auf Seite 187. Wegen der unveränderten Spezifikation dürften Sie keine Veränderung des Programmverhaltens wahrnehmen.

9.3.4. Rekursive Abarbeitung einer Liste

Seite 205

public void kopieren(final StapelListe quelle) throws VollAusn { // (9.11)
	anker = knotenKopie(quelle.anker);
}
private Knoten knotenKopie(final Knoten qu) throws VollAusn {
	try {
		Knoten ziel = new Knoten(qu.wert); // throws NullPointerException bei qu == null
		ziel.verbindung = knotenKopie(qu.verbindung); // rekursiver Aufruf ¬
		return ziel;
	}
	catch (NullPointerException ausnahme) {
		return null;
	} // Liste zu Ende
	catch (OutOfMemoryError ausnahme) { // kein Speicher mehr frei
		throw new VollAusn();
	}
}
public boolean gleich(final StapelListe stapel) { // const
	return knotenVergleich(anker, stapel.anker);
}
private boolean knotenVergleich(final Knoten erster, final Knoten zweiter) {
	try {
		return erster.wert == zweiter.wert &
			// throws NullPointerException, wenn eine Referenz == null
			knotenVergleich(erster.verbindung, zweiter.verbindung); // ¬
	}
	catch (NullPointerException ausnahme) { // eine der Listen zu Ende
		return erster == null & zweiter == null; // ob auch die andere?
	}
}

Abb. 9.4: Rekursive Abarbeitung

b.kopieren(a);

Abb. 9.5: Stapel bei Rekursion

Übung 9.5: Implementieren Sie die Kopieroperation und den Vergleich des Stapels mit der Reihungsimplementierung aus dem Programm (9.8) auf Seite 201 mit der Technik der rekursiven Abarbeitung.

Übung 9.6: Erstellen Sie einen Ganzzahlstapel als Reihung und erweitern Sie ihn so, dass er auch eine Funktion exportiert, die die Summe aller gestapelten Zahlen liefert. Implementieren Sie diese Operation rekursiv.

9.3.5. Vorwärts verkettete Listen

Seite 209

class WarteschlangeListe implements Warteschlange { // (9.12)
	 // (polymorphe) Listenimplementierung von Warteschlange
	private class Knoten { ... // innere Klasse wie im Programm (9.9)
	private Knoten aelteste, juengste;
	public WarteschlangeListe() {
		aelteste = null;
		juengste = null;
	}
...

Abb. 9.6: Vorwärts verkettete Liste

public void entleeren() {
	aelteste = null;
}
public Object lesen () throws LeerAusn {
	try { return aelteste.wert; }
	catch (NullPointerException ausnahme) { throw new LeerAusn(); }	
}
public void entfernen() throws LeerAusn {
	try { aelteste = aelteste.verbindung; }
	catch (NullPointerException ausnahme) { throw new LeerAusn(); }	
}
public boolean leer(){
	return aelteste == null;
}
public boolean voll() {
	try {
		Knoten knoten = new Knoten(); // new throws OutOfMemoryError
		return false; // ist gelungen
	} catch (OutOfMemoryError ausnahme) {
		return true; // kein Platz mehr
	}
}
private Knoten neu; // global für den Ausnahmebehandlungsteil
public void eintragen (final Object element) throws VollAusn {
	try {
		neu = aelteste.verbindung;
		// throws NullPointerException im Sonderfall, wenn Warteschlange noch leer ¬
		neu = new Knoten(element, aelteste); // throws OutOfMemoryError
		neu.verbindung = null; // zurücksetzen
		juengste.verbindung = neu; // neuer jüngster Knoten wird eingefügt
	}
	catch (OutOfMemoryError ausnahme) {	throw new VollAusn(); }
	catch (NullPointerException ausnahme) { // Sonderfall: Warteschlange leer
		juengste = neu;
		aelteste = neu;
	}
}

Übung 9.7: Implementieren Sie die Kopieroperation und den Vergleich der Listenimplementierung der Warteschlange mit der Technik der rekursiven Abarbeitung.

Übung 9.8: Eine Warteschlange kann auch mit einem Anker als ein vorwärts verketteter Ring implementiert werden. Der Anker referiert den jüngsten Knoten; dessen Verkettung referiert den ältesten Knoten. Implementieren Sie nun die Warteschlange auf diese Weise.

9.3.6. Doppelt verkettete Listen

Seite 211

class Knoten {
	Object wert;
	Knoten vorwaerts, rueckwaerts; // Vor- und Rückwärtsverkettung ¬
}

Abb. 9.7: Doppelt verkettete Liste

public class PosListePol implements PosListe { // (9.13)
		// positionierbare Liste, polymorphe Implementierung
	private class Knoten {
		Object wert;
		Knoten vorwaerts, rueckwaerts; // Vor- und Rückwärtsverkettung
		Knoten(Object wert, Knoten vorwaerts, Knoten rueckwaerts) {
			this.wert = wert;
			this.vorwaerts = vorwaerts;
			this.rueckwaerts = rueckwaerts;
		}
	}
	private Knoten anfang, ende, aktuell; // ¬
	public PosListePol() {
		entleeren();
	}
	public void entleeren() { // macht die Liste leer
		anfang = null; ende = null; aktuell = null;
	}
	public void eintragen(final Object element) throws VollAusn {
		// trägt element hinter die aktuelle Position ein
		try { ... // hier nur der allgemeine Fall mitten in der Kette:
			Knoten neu = new Knoten(element, aktuell.vorwaerts, aktuell);
			// neuer Knoten zeigt vorwärts auf den aktuellen, rückwärts auf den dahinter
			aktuell.vorwaerts.rueckwaerts = neu; // nach dem aktuellen einfügen
			aktuell.vorwaerts = neu;
			aktuell = neu;
		} catch (OutOfMemoryError ausnahme) { throw new VollAusn(); }
	}
	public void erstesEintragen(final Object Element) { ... // ähnlich
	public void loeschen() throws LeerAusn {
		try { ... // hier nur der allgemeine Fall mitten in der Kette:
			Knoten hinten = aktuell.rueckwaerts;
				// throws NullPointerException, wenn Liste leer: aktuell = null
			Knoten vorne = aktuell.vorwaerts;
				// throws NullPointerException, wenn Liste leer
			hinten.vorwaerts = vorne;
			vorne.rueckwaerts = hinten;
			aktuell = hinten;
		} catch (NullPointerException ausnahme) { throw new LeerAusn(); }
	}
	public Object aktuellesElement() throws LeerAusn {
		try {
			return aktuell.wert; // throws NullPointerException
		} catch (NullPointerException ausnahme) { throw new LeerAusn(); }
	}
	public void vorwaerts() throws LeerAusn {
		try { aktuell = aktuell.vorwaerts; }
		catch (NullPointerException ausnahme) { throw new LeerAusn(); }
	}
	public void rueckwaerts() throws LeerAusn { ... // ähnlich
	public void anfang() {
		aktuell = anfang;
	}
	public void ende() { ... // ähnlich
	public boolean leer(){
		return anfang == null;
	}
	... // weitere Methoden
}

Übung 9.9: Programmieren Sie die Methoden kopieren, gleich und suchen der positionierbaren Liste mit Hilfe der Ausprägung des iterators.

9.3.7. Binärbäume

Seite 212

Abb. 9.8: Binärbaum

public class Binaerbaum { // (9.14)
	protected class Knoten {
		private Object wert;
		private Knoten links, rechts;
		public Knoten (Object wert, Knoten links, Knoten rechts) {
			this.wert = wert;
			this.links = links;
			this.rechts = rechts;
		}
	}
	protected Knoten wurzel;
	... // Zugriffsmethoden
}

Übung 9.10: Erweitern Sie die Klasse Binaerbaum mit Navigationsoperationen.

9.3.8. Mehrfach verkettete Listen

Seite 213

Abb. 9.9: Aufzählungsobjekte

private class Knoten { // geschachtelte Klasse innerhalb von Aufz // (9.15)
	int pos;
	Knoten naechster, vorheriger, erster, letzter; // Komponenten für die Verkettung
	String name; // Name des Wertobjekts
}
private Knoten knoten;
private static Aufz ersterAkt, letzterAkt; // Randknoten für den aktuellen Typ
private static Bool neuerTyp;
protected Aufz() { // geschützter Konstruktor, nur für die Erzeugung eines neuen Werts
	try {
		knoten = new Knoten();
		neuerTyp = new Bool(letzterAkt == null || ! getClass().isInstance(letzterAkt));
		neuerTyp = neuerTyp.naechster(); // Ausnahme beim ersten Knoten provozieren
		// neuer Aufzählungswert für denselben Typ, verketten:
		knoten.pos = letzterAkt.knoten.pos + 1; // durchnummerieren
		knoten.vorheriger = letzterAkt;
		letzterAkt.knoten.naechster = this;
	}
	catch (BereichAusn ausnahme) {
		// mit dem ersten Knoten eines Aufzählungstyps wird eine neue Kette begonnen
		knoten.pos = 0;
		ersterAkt = this;
	}
	letzterAkt = this; // neuer letzter Wert für den aktuellen Typ
	knoten.erster = ersterAkt;
	// setzen Komponente letzter auf letzterAkt in allen Knoten des aktuellen Typs:
	setzen(knoten); // s. Programm (13.8)
	// setzen Komponente name mit dem Namen des aktuellen Werts:
	knoten.name = namenErmitteln(); // s. Programm (13.8)
}
public static final Farbe GRUEN = new Farbe();

9.3.9. Dynamische Reihungen

Seite 214

public class SeqDateiVektor implements SeqDatei { // hier ohne Ausnahmen // (9.16)
	private java.util.Vector inhalt = new java.util.Vector(); // ¬
	public void neuBeschreiben() {
		inhalt.removeAllElements(); // ¬
	}
	public void zuruecksetzen() {
		index = 0;
	}
	private int index = 0;
	public void eintragen(final Object element) {
		inhalt.addElement(element); // wird ans Ende angehängt ¬
	}
	public void naechstesElement() {
		index ++;
	}
	public Object aktuellesElement() throws DateiendeAusn {
		try {
			return inhalt.elementAt(index); // throws ArrayIndexOutOfBoundsException ¬
		} catch (ArrayIndexOutOfBoundsException ausnahme) {
			throw new DateiendeAusn();
		}
	}
	public boolean endeDerDatei() {
		return index == inhalt.size();
	}
}

Übung 9.11: Implementieren Sie die Schnittstelle Warteschlange mit Hilfe einer dynamischen Reihung.


Multibehälter Inhaltsverzeichnis Hypertext-Version Steuerstrukturen © APSIS GmbH