2__08.rtfd -- /usr/axel/vorl/java97/skript/skript

Werbung
62
Copyright 1996-1997 by Axel T. Schreiner. All Rights Reserved.
Verschachtelte und anonyme Klassen
Im ersten Beispiel modellieren mehrfach verschachtelte Klassen eine konventionellere Lösung
mit verschachtelten Funktionen. Anonyme Klassen spezialisieren abstrakte Klassen, so daß
sie instantiiert werden können.
Das zweite Beispiel zeigt, wie man die Argumente der Kommandozeile als StringReader
bearbeiten kann. Ein Programm kann dann zum Beispiel wahlweise Eingabezeilen oder
Kommandoargumente mit den gleichen Methoden verarbeiten.
Arithmetische Ausdrücke bewerten — char/Expression
Expression liest Zeilen mit arithmetischen Ausdrücken von der Standard-Eingabe und
bewertet sie mit float-Arithmetik.
Expression demonstriert den Umgang mit StreamTokenizer zur Analyse eines Texts.
Expression erweitert Number
und erlaubt damit den Einsatz von Ausdrücken an Stelle von
Konstanten.
{programs/char/Expression.java}
import java.io.*;
/** A class to store and evaluate arithmetic expressions */
public abstract class Expression extends Number {
/** reads lines from stdin and evaluates them */
public static void main (String args []) {
StreamTokenizer s = new StreamTokenizer(new InputStreamReader(System.in));
s.commentChar(’#’);
// ignore # to eol
do
try {
Number n = Expression.parse(s);
if (n != null) System.out.println(n.floatValue());
} catch (java.lang.Exception e) {
System.err.println(s +": "+ e);
try {
while (s.nextToken() != s.TT_EOF && s.ttype != s.TT_EOL)
;
} catch (IOException ioe) { }
}
while (s.ttype == s.TT_EOL);
}
{}
sollte auf einen Reader aufgesetzt werden und gibt dann Symbole als
in ttype ab. Das Verhalten kann durch Definition verschiedener
Zeichenklassen beeinflußt werden, aber es eignet sich wohl am besten zur Zerlegung einer
Art Programmiersprache.
StreamTokenizer
nextToken()
und
63
Fehler
{programs/char/Expression.java}
/** A class to describe parsing exceptions */
public static class Exception extends java.lang.Exception {
public Exception (String msg) { super(msg); }
}
{}
Bei Fehlern wird abgebrochen; der Aufrufer kann dann zum Beispiel den Rest einer
Eingabezeile beseitigen.
Die Fehler werden mit einer neuen, verschachtelt definierten Exception-Klasse berichtet.
StreamTokenizer
liefert als toString()
eine Positionsangabe.
Arithmetik
{programs/char/Expression.java}
/** map arithmetic to preferred types */
public byte byteValue () { return (byte)intValue(); }
public double doubleValue () { return floatValue(); }
public long longValue () { return intValue(); }
public short shortValue () { return (short)intValue(); }
{}
Expression soll einen Ausdruck als Baum speichern und sich wie eine Number
bewerten
lassen.
Um die Zahl der nötigen Methoden zu reduzieren, soll nur in int und float gerechnet werden.
Die anderen Methoden, die eine Unterklasse von Number implementieren muß, werden für alle
Unterklassen umgelenkt.
Expression
ist noch immer abstract, denn intValue() und floatValue() fehlen.
64
Zeilen
{programs/char/Expression.java}
/** expr: sum \n */
public static Number parse (StreamTokenizer s)
throws Exception, IOException {
s.eolIsSignificant(true);
for (;;)
switch (s.nextToken()) {
default:
Number result = Sum.parse(s);
if (s.ttype != s.TT_EOL) throw new Exception("expecting nl");
return result;
case s.TT_EOL:
continue;
case s.TT_EOF:
return null;
}
}
{}
Expression.parse() soll eine Summe von einer Zeile speichern.
Damit das Ende der Eingabe als null berichtet werden kann, ist parse() nicht als Konstruktor
ausgeführt.
Da Expression eigentlich nur die Ableitung von Number vereinfacht, sind alle Resultate Numberund nicht Expression-Objekte. Außerdem kann man dann als Faktor direkt eine Number liefern.
65
Verschachtelung
{programs/char/Expression.java Sum}
/** sum: product { +- product } */
protected abstract static class Sum extends Expression {
protected Number left, right;
protected Sum (Number left, Number right) {
this.left = left; this.right = right;
}
{}
{programs/char/Expression.java product}
/** product: term { *%/ term } */
protected abstract static class Product extends Sum {
protected Product (Number left, Number right) {
super(left, right);
}
{}
{programs/char/Expression.java term}
}
// end Product
}
// end Sum
/** term: +term | -term | (sum) | number */
protected abstract static class Term extends Expression {
protected Number arg;
protected Term (Number arg) { this.arg = arg; }
{}
{programs/char/Expression.java wrap}
}
// end Term
}
// end Expression
{}
Expression enthält Sum — Knoten mit zwei Abkömmlingen — und Term — Knoten mit einem
Abkömmling. Sum enthält Product.
Alle diese Klassen sind abstract — erst Spezialisierungen für die Operatoren implementieren
die fehlenden Methoden.
Objekte können hier nur in geschachtelten Unterklassen oder lokal mit parse() erzeugt
werden. Da die Klassen protected sind, kann auch parse() trotz public nicht verschleppt
werden.
66
Summe erkennen und bewerten
{programs/char/Expression.java Sum}
/** sum: product { +- product } */
public static Number parse (StreamTokenizer s)
throws Exception, IOException {
s.ordinaryChar(’-’); s.ordinaryChar(’/’);
Number result = Product.parse(s);
for (;;)
switch (s.ttype) {
case ’+’:
s.nextToken();
result = new Sum(result, Product.parse(s)) {
public float floatValue () {
return left.floatValue() + right.floatValue();
}
public int intValue () {
return left.intValue() + right.intValue();
}
};
continue;
case ’-’:
s.nextToken();
result = new Sum(result, Product.parse(s)) {
public float floatValue () {
return left.floatValue() - right.floatValue();
}
public int intValue () {
return left.intValue() - right.intValue();
}
};
continue;
default:
return result;
}
}
{}
Addition und Subtraktion unterscheiden sich nur in der Bewertung, folglich werden anonyme
Klassen zur Modellierung verwendet.
Konstruktoren darf man in anonymen Klassen nicht ersetzen, aber das ist hier auch nicht
nötig.
In den Quellen sieht man, daß StreamTokenizer das Zeichen / für Kommentare
parseNumbers()
vergibt. Das muß man hier rückgängig machen.
und - durch
67
Produkt
{programs/char/Expression.java product}
/** product: term { *%/ term } */
public static Number parse (StreamTokenizer s)
throws Exception, IOException {
Number result = Term.parse(s);
for (;;)
switch (s.ttype) {
case ’*’:
s.nextToken();
result = new Product(result, Term.parse(s)) {
public float floatValue () {
return left.floatValue() * right.floatValue();
}
public int intValue () {
return left.intValue() * right.intValue();
}
};
continue;
case ’/’:
s.nextToken();
result = new Product(result, Term.parse(s)) {
public float floatValue () {
return left.floatValue() / right.floatValue();
}
public int intValue () {
return left.intValue() / right.intValue();
}
};
continue;
case ’%’:
s.nextToken();
result = new Product(result, Term.parse(s)) {
public float floatValue () {
return left.floatValue() % right.floatValue();
}
public int intValue () {
return left.intValue() % right.intValue();
}
};
continue;
default:
return result;
}
}
{}
68
Term
{programs/char/Expression.java term}
/** term: +term | -term | (sum) | number */
public static Number parse (StreamTokenizer s)
throws Exception, IOException {
switch (s.ttype) {
case ’+’:
s.nextToken();
return Term.parse(s);
case ’-’:
s.nextToken();
return new Term(Term.parse(s)) {
public float floatValue () { return - arg.floatValue(); }
public int intValue () { return - arg.intValue(); }
};
case ’(’:
s.nextToken();
Number result = Sum.parse(s);
if (s.ttype != ’)’) throw new Exception("expecting )");
s.nextToken();
return result;
case s.TT_NUMBER:
result = s.nval == (int)s.nval ?
(Number)new Integer((int)s.nval) : (Number)new Float(s.nval);
s.nextToken(); return result;
}
throw new Exception("missing term");
}
{}
+
wird einfach ignoriert, - als Vorzeichen mit einer anonymen Klasse implementiert.
Klammern führen zur Rekursion — aber Sum ist ausreichend sichtbar.
Leider liefert der StreamTokenizer in nval nur einen double-Wert. Hier wird angedeutet, daß
der Expression-Baum durchaus verschiedene Number-Objekte enthalten kann, da Number
Wertabfragen in alle arithmetischen Zieltypen verlangt.
Java verbindet implizite Umwandlung mit Overloading, deshalb funktioniert die Konstruktion
der Float-Number aus dem double-Wert nval.
69
Arithmetische Ausdrücke auf der Kommandozeile — char/Expr
Expr erweitert Expression um die Fähigkeit, auch einen Ausdruck auf der Kommandozeile zu
bewerten.
Expr demonstriert, daß man mit einem StringBuffer die Kommandozeile in einen String
zurückverwandeln kann, aus dem dann mit einem StringReader ein Reader wird, den man
mit den Methoden von Expression bearbeiten kann.
{programs/char/Expr.java}
import java.io.*;
/** A class to mimic part of the expr command */
public abstract class Expr extends Expression {
/** evaluates one expression on the command line
* or expression lines from standard input
*/
public static void main (String args []) {
if (args == null || args.length == 0)
Expression.main(args);
else {
// turn command line into Reader
StringBuffer b = new StringBuffer(args[0]);
for (int n = 1; n < args.length; ++ n) b.append(" ").append(args[n]);
b.append("\n");
StreamTokenizer s = new StreamTokenizer(new StringReader(b.toString()));
try {
Number n = parse(s);
if (n != null) System.out.println(n.intValue());
} catch (java.lang.Exception e) {
System.err.println(s +": "+ e);
}
}
}
}
{}
Expr.java befindet sich im gleichen Katalog und anonymen Paket wie Expression.java, deshalb
muß nicht importiert werden.
Klassenmethoden wie main() und parse() werden vererbt, aber man kann sie auch über die
Klasse aufrufen. super kann man in einer Klassenmethode — ebenso wie this — nicht
verwenden, da eine Klassenmethode anders als in Objective C keinen Empfänger hat. Der
einzige Effekt der Vererbung ist, daß man parse() direkt und nicht als Expression.parse()
aufrufen kann.
Obgleich Expr noch abstract ist, kann man main() und parse() verwenden.
Herunterladen