© APSIS GmbH , Polling, 2000
in der 2. Auflage des Lehrbuchs Programmieren mit Java gegenüber der 1. Auflage
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.
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.
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.
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);
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.
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:
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; }
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
Ein neuer Kapitel wurde hinzugefügt:
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))
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.
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 } }
Neue Einträge (insbesondere Standardklassen und Methoden aus Standardklassen) wurden hinzugefügt
© APSIS GmbH , Polling, 2000