© APSIS GmbH extern.gif (1249 Byte), Polling, 2000


Ergänzungen

in der 2. Auflage des Lehrbuchs Programmieren mit Java gegenüber der 1. Auflage


Seite 22, Kapitel 2.2.2

Unter dem Bild (vor der Übung) wurde folgender Text hinzugefügt:

Der Sinn der Namensräume ist also, einen und denselben Namen in verschiedenen Klassen (i.A. für unterschiedliche Zwecke) vergeben zu können. Somit sind die Programmierer einzelner Klassen unabhängig voneinander und sie müssen die Namensvergabe untereinander nicht koordinieren.


Seite 30, Kapitel 2.2.11

Vor die Übung 2.10 wurde eingefügt:

Streng genommen sind also überladene Methoden unterschiedliche Methoden. Eigentlich wird nicht die Methode überladen, sondern nur ihr Name. In der Praxis spricht man aber von überladenen Methoden, weil sie - meistens - einem und demselben (oder zumindest einem ähnlichen) Zweck dienen, nur mit unterschiedlichen Parametern aufzurufen sind.


Seite 75, Kapitel 3.4.4

Am Ende des Kapitels (vor den Übungen) wurde folgender Text hinzugefügt:

Bemerkung: Eine Prozedur wird in Java als Methode einer Klasse implementiert (s. Kapitel 3.1). Wenn sie (auch) aus static-Methoden (z.B. aus main) aufgerufen wird, muß sie als static-Methode vereinbart werden. Wenn sie nur aus nicht-static-Methoden aufgerufen wird, kann sie als nicht-static-Methode vereinbart werden. Nicht-static-Methoden können aber nur mit einem Objekt (erreicht durch eine Referenz) aufgerufen werden. Wenn der Aufruf einer nicht-static-Prozedur ohne Referenz erfolgt, wird implizit die Referenz this angenommen. Sie referiert immer das aktuelle Objekt, für welches die Methode aufgerufen wurde.


Seite 114, Kapitel 5.5.5

Nach dem letzten Programm (vor dem letzten Abschnitt) auf der Seite wurde folgender Hinweis eingefügt:

Eine solche Wertereferenz (wie jede andere public static Referenz) kann in einem Benutzerprogramm mit

Klasse.WERT

angesprochen werden:

eimer.fuellen(Eimer.WASSER);

Seite 127, Kapitel 6.2.8

Am Ende des Kapitels wurde folgender Text hinzugefügt:

Ein Menü funktioniert über Polymorphie.

Die (nicht-aufgeschobene) Oberklasse enthält die Methode menue; von hier aus werden die (leeren) Rückrufprozeduren aufgerufen:

public void menue() {
	...
	rueckrufprozedur();
	...
}
protected void rueckrufprozedur() { }

Ohne Polymorphie würde hier immer die leere Rückrufprozedur aufgerufen werden:

Menue m = new MeinMenue();
m.menue(); // ruft rueckrufprozedur zurück

Weil aber bei der Auswahl der Klasse, aus welcher die Methode aufgerufen wird, aufgrund des Typ des Objekts (hier: MeinMenue) und nicht der Referenz (hier: Menue) erfolgt, ruft menue nicht die leere, sondern die überschreibende rueckrufprozedur (nicht aus der Oberklasse, sondern aus der Unterklasse) zurück.


Seite 148, Kapitel 7.1.3

vor der Bemerkung wurde der Abschnitt eingefügt:

Die lästige Konvertierung der Ergebnisse von Aufzählungsmethoden kann umgegangen werden, wenn die Referenz nicht von der eigentlichen Aufzählungsklasse, sondern von ihrer Oberklasse lehrbuch.Aufz angelegt wird:

  • farbe = farbe.naechster();
    Aufz farbe = Farbe.ROT;
    Aufz andereFarbe = farbe.erster();
  • Hier ist es sichtbar, warum es wichtig ist, einer Aufzählungsvariable einen Vorbesetzungswert zuzuweisen: Das referierte Objekt ist ausschlaggebend, aus welcher Aufzählungsklasse die Werte für erster() oder naechster() genommen werden .

    Später, im gesamten Lehrbuch wurden Aufzählungsreferenzen vom Typ Aufz vereinbart und die Typkonvertierungen weggelassen werden:

    Seite 149, Kapitel 7.1.3, Programm (7.5)

    package lehrbuch; // (7.5)
    class Getraenk extends Aufz {
    	public static final Getraenk WASSER = new Getraenk();
    	public static final Getraenk WEIN = new Getraenk();
    }
    class Gefuellt extends Aufz {
    	public static final Gefuellt NEIN = new Gefuellt();
    	public static final Gefuellt JA = new Gefuellt();
    }
    public class EinEimer {
    	private static Aufz eimerGefuellt = Gefuellt.NEIN; // oder JA
    	private static Getraenk eimerInhalt = Getraenk.WASSER; // oder WEIN
    	// eimerInhalt kann auch vom Typ Aufz vereinbart werden, kein Unterschied
    	// öffentliche Konstanten:
    	public static final Getraenk WASSER = Getraenk.WASSER;
    	public static final Getraenk WEIN = Getraenk.WEIN;
    	// Mutatoren:
    	public static void fuellen(final Getraenk getraenk) throws VollFehler{
    		try {
    			eimerGefuellt = eimerGefuellt.naechster(); // throws BereichAusn ¬
    			// keine Typkonvertierung nötig, weil eimerGefuellt vom Typ Aufz vereinbart wurde
    			eimerInhalt = getraenk;
    		} catch (BereichAusn ausnahme) { // bei JA
    			throw new VollFehler(); // ¬
    		}
    	}
    	public static void fuellen() throws VollFehler {
    		fuellen(WASSER);
    	}
    	public static void entleeren() throws LeerFehler {
    		try {
    			eimerGefuellt = eimerGefuellt.vorheriger(); // throws BereichAusn ¬
    			// keine Typkonvertierung nötig, weil eimerGefuellt vom Typ Aufz vereinbart wurde
    		} catch (BereichAusn ausnahme) { // bei NEIN
    			throw new LeerFehler(); // ¬
    		}
    	}
    	// Informator:
    	public Getraenk inhalt() throws LeerFehler {
    		try {
    			attrappe = eimerGefuellt.vorheriger(); // BereichAusn ¬
    			// keine Typkonvertierung nötig, weil eimerGefuellt vom Typ Aufz vereinbart wurde
    			// attrappe ist global, damit der Compiler es nicht wegoptimiert
    			return eimerInhalt;
    		} catch (BereichAusn ausnahme) {
    			throw new LeerFehler(); // ¬
    		}
    	}
    	private static Gefuellt attrappe;
    }

    Seite 184, Kapitel 8.1.3, Programm (8.18)

    class Patient extends lehrbuch.Aufz { // (8.18)
    	public static final Patient MAYER = new Patient();
    	public static final Patient MUELLER = new Patient();
    		... // alle Patienten werden aufgelistet
    }
    public class Arztpraxis extends MenueArzt { // generiert mit zwei Menüpunkten
    	private lehrbuch.kapitel8.Warteschlange liste =
    		new lehrbuch.kapitel8.WarteschlangeGen(Patient.MAYER, 20);
    	protected void patientKommt() { // Rückrufprozedur für den ersten Menüpunkt
    		try {
    			lehrbuch.Aufz patient = Patient.MAYER;
    			patient = patient.eingabe(); // throws BereichAusn;
    			// keine Typkonvertierung nötig, weil patient vom Typ Aufz vereinbart wurde
    			liste.eintragen(patient); // throws VollAusn; // ¬
    		}
    		catch(lehrbuch.BereichAusn ausnahme) {
    			lehrbuch.Programm.meldung("Patient unbekannt", "Tippfehler");
    		}
    		catch(lehrbuch.kapitel8.VollAusn ausnahme) {
    			lehrbuch.Programm.meldung("Draußen warten", "Wartezimmer voll");
    		}
    	}
    	protected void dieNaechsteBitte() { // Rückruf für den zweiten Menüpunkt
    		try {
    			lehrbuch.Aufz patient = liste.lesen(); // throws LeerAusn;
    			// keine Typkonvertierung nötig, weil patient vom Typ Aufz vereinbart wurde
    			patient.meldung("Patient ausrufen");
    			liste.entfernen(); // Patient wird aus der Liste ausgetragen ¬
    		}
    		catch(lehrbuch.kapitel8.LeerAusn ausnahme) {
    			lehrbuch.Programm.meldung("Feierabend"); // kein Patient wartet
    		}
    	}
    } // menue wird nun mit patientKommt und dieNaechsteBitte von start aufgerufen

    Seite 162, Kapitel 7.6.3

    Ein neuer Kapitel wurde hinzugefügt:

    7.6.3. Die Syntax von Ausdrücken

    Unter einem Primärausdruck verstehen wir ein Literal, den Namen einer (evtl. konstanten, d.h. final) Variablen oder einen Funktionsaufruf (ohne oder mit Parameter) oder einen in runde Klammer eingeschlossenen Ausdruck. Der Begriff Ausdruck wird rekursiv definiert: Er ist entweder ein Primärausdruck (wie oben definiert) oder ein Operationszeichen mit (vielleicht Primär-) Ausdrücken als Parameter.

    Durch die (hier vereinfachte) Syntax der Sprache wird exakt definiert, was ein (logischer) Ausdruck ist:

    Ausdruck ::= Relation { Operator Relation }
    Operator ::= & | | | ^
    Relation ::= Einfacher_Ausdruck [ Relationaler_Operator Einfacher_Ausdruck ]
    Relationaler_Operator ::= < | <= | == | >= | > | !=
    Einfacher_Ausdruck ::= [ ! ] Primärausdruck
    Primärausdruck ::= Name | Funktionsaufruf | (Ausdruck )
    Funktionsaufruf ::= Name ( [ Aktuelle_Parameter ] )
    Aktuelle_Parameter ::= Ausdruck {, Ausdruck }

    In der letzten Zeile taucht in der Definition von Ausdruck über den Primärausdruck der geklammerte Ausdruck wiederholt auf. Dieses Phänomen heißt – ähnlich wie die sich selbst aufrufende Prozedur – Rekursion.

    Die Syntax beschreibt nicht, von welchen Typen diese Parameter sein sollen. Dies wird durch die sog. Kontextbedingungen überprüft und in der Sprachbeschreibung nicht formal, sondern in natürlicher Sprache (deutsch oder englisch) definiert.

    Übung: Überprüfen Sie mit Hilfe der obigen Syntax, ob die folgenden drei Programmausschnitte einen gültigen Ausdruck bilden. Falls Sie einen syntaktischen Fehler finden, verbessern Sie den Ausdruck zu einem gültigen:

    heute == montag & (! morgen == sonntag) & lieferung(drucker) != angekommen
    heute >= montag | ((morgen > donnerstag) & ! (lieferung(compiler) == angekommen))
    heute >= montag | (morgen > donnerstag ! & lieferung() == angekommen))

    Seite 234, Kapitel 10.3.3

    Am Ende des Kapitels wurde folgender Text hinzugefügt:

    Wiederholungen und Fallunterscheidungen können ineinander geschachtelt werden. Ein Beispiel hierfür sei ein Menü, d.h. ein Ausschnitt aus der Methode menue der vom Menügenerator generierten Klasse:

    public void menue() {
    	int auswahl;
    	... // Nummer des ausgewählten Menüpunkts einlesen
    	while (auswahl != 6) { // letzter Menüpunkt 6 = Ende
    		switch (auswahl) {
    			case 0: rueckrufprozedur1(); break;
    				...
    			case 5: rueckrufprozedur6(); break;
    			default: throw new ProgrammFehler();
    		}
    	}
    }

    Die Anzahl der Fälle (und somit das Endekriterium der Schleife) sowie die Namen der Rückrufprozeduren hängen natürlich von der Eingabe des Menügenerators ab.


    Seite 235, Kapitel 10.3.6, Programm (10.28)

    Die Methode mischen wird jetzt mit Hilfe der im Kapitel 8.3.8 auf Seite 189 eingeführten Multibehälter lehrbuch.kapitel8.SeqDatei vorgestellt:

    import lehrbuch.kapitel8.SeqDatei;
    public void mischen( // vorsortierte Dateien werden gemischt // (10.28)
    		final SeqDatei eingabe1, final SeqDatei eingabe2, SeqDatei ausgabe) {
    	Element puffer1 = new Element(), puffer2 = new Element(); // geordnet
    	eingabe1.zuruecksetzen();
    	eingabe2.zuruecksetzen();
    	ausgabe.neuBeschreiben();
    	if (! eingabe1.endeDerDatei())
    		puffer1 = eingabe1.aktuellesElement(); // jeweils erstes Element einlesen
    	if (! eingabe2.endeDerDatei())
    		puffer2 = eingabe2.aktuellesElement();
    	while (! eingabe1.endeDerDatei() && ! eingabe2.endeDerDatei()) {
    		if (puffer1.kleiner(puffer2)) { // Operation für Element
    			ausgabe.eintragen(puffer1); // den kleineren ausschreiben
    			eingabe1.naechstesElement()
    			puffer1 = eingabe1.aktuellesElement(); // und wieder einlesen
    		}
    		else {
    			ausgabe.eintragen(puffer2);
    			eingabe2.naechstesElement()
    			puffer2 = eingabe2.aktuellesElement(); // und wieder einlesen
    		}
    	} // band1 oder band2 ist zu Ende
    	// Rest kopieren:
    	while (!band1.eof()) {
    		ausgabe.eintragen(puffer1); // den kleineren ausschreiben
    		eingabe1.naechstesElement()
    		puffer1 = eingabe1.aktuellesElement(); // und wieder einlesen
    	}
    	while (!band2.eof()) {
    		ausgabe.eintragen(puffer2);
    		eingabe2.naechstesElement()
    		puffer2 = eingabe2.aktuellesElement(); // und wieder einlesen
    	}
    }

    Seite 329, Sachwortverzeichnis

    Neue Einträge (insbesondere Standardklassen und Methoden aus Standardklassen) wurden hinzugefügt


    © APSIS GmbH extern.gif (1249 Byte), Polling, 2000