Kap.11: FEHLERTOLERANTES PROGRAMMIEREN
Kapitel-Index
11.1 Ausnahmebehandlung (try)
11.1.1 Auswerfen einer Ausnahme (throw)
11.1.2 Abfangen einer Ausnahme (catch)
11.1.3 Das letzte Wort behalten (finally)
11.2 Testfragen
Das Konzept der Ausnahmebehandlung (11.1) stammt aus Ada, hat
aber Vorläufer in COBOL und PL/I. Bei Funktionen kann das return
-Konzept (7.6) eine Alternative zur Ausnahmebehandlung sein, z.B.
read() (8.2) mit Ausnahmewert -1.
Ausnahmen sind Objekte von Klassen, die direkt oder indirekt
Subklassen von Throwable sind.
----------------------------------------------------------------
java.lang.Object
java.lang. T h r o w a b l e
java.lang. E x c e p t i o n
java.io.IOException
z.B.
z.B.
java.lang.
java.io.
-
compile-
ClassNotFoundException
FileNotFoundException
- time-
-
checked
java.io.
-
InterruptedIOException
------------
compile-
java.lang.RuntimeException java.lang.Error
- time-
z.B. z.B.
un-
java.lang. java.lang.Class
checked
NullPointerException CircularityError
java.util.
EmptyStackException -
----------------------------------------------------------------
Fig. 11: Hierarchie der Ausnahme-Typen
Man unterteilt Ausnahmen in "...Exception", von denen sich das
Programm bei geeigneter Ausnahme-Behandlung erholen kann, und in
"...Error", von denen sich das Programm i.a. nicht erholen kann.
Außerdem unterscheidet man Ausnahmen, die bereits zur Übersetzungszeit geprüft werden (compiletime-checked) von solchen, die
nicht bereits zur Übersetzungszeit geprüft werden, weil dies zu
schwierig oder unmöglich ist, z.B. Erkennen von Totschleifen im
Allgemeinen.
--------------------------------------
VVVVVVVVVVVVV
Benutzer-eigene Ausnahmen (Prinzip)
VVVVVVVVVVVVV
VV
--------------------------------------
VV
VV VV
VV Nach Konvention kann der Benutzer vereinbaren: VV
VV VV
VV a) Eigene zur Übersetzungszeit geprüfte (checked) Ausnahmen VV
VV direkt als (ggf. Subklasse von) java.lang.Exception VV
VV VV
VV b) Eigene nicht zur Übersetzungszeit geprüfte Ausnahmen VV
VV als (ggf. Subklasse von) java.lang.RuntimeException, d.h. VV
VV indirekt als Subklasse von java.lang.Exception VV
VV VV
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
11.1 Ausnahmebehandlung (try)
Ausnahmebehandlung (exception handling) ist geeignet zur Bewältigung von Fehler-Situationen, z.B. unzulässige Parameterwerte,
und hat zum Ziel, das Programm für klassenhierarchisch modulares
(9) und synchrones paralleles (12) Programmieren sicherer zu machen.
Wer Ausnahmebehandlung einprogrammiert, schützt den nachfolgenden
Programmierer vor bösen Überraschungen, bürdet ihm jedoch auch die
Last auf, die ausgeworfenen Ausnahmen abfangen zu müssen. Man
würde sich unbeliebt machen, wenn man zu erwartende Standardsituationen, z.B. Erreichen des Datei-Endes, als Ausnahmen auswirft und
das Abfangen dem Nachfolger überläßt.
Ausnahmebehandlung mit Auswerfen (throw, 11.1.1) und Abfangen
(catch, 11.1.2) sowie ggf. Endbehandlung (finally, 11.1.3) ist nur
möglich innerhalb einer try-Anweisung.
Im Catches-Teil einer try-Anweisung (siehe unten) müssen alle
Ausnahmen abgefangen werden, die im try-Block explizit mit einer
throw-Anweisung (11.1.1) oder implizit in einer Methode/Konstruktor mit Throws (Syntax.083/089, Syntax.082) ausgeworfen wurden.
Eine try-Anweisung (TryStatement) ist nach Syntaxdiagramm
069 (für Statement, hier ein Auszug Syntax.069°) von der Form
069°:TryStatement TryAnweisung
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
H Try- H
H
-------
H
H -
try ------------------------------
Block
-
H
H
-------
H
H C a t c h e s
H
H
----------------------------------------------
H
H
H
H
Throwable
H
H
Exception- Catch-
H
H
-----------
-------
H
H
-
catch -
( -
Formal-
-
) -
Block
-
H
H
Parameter
-------
H
H
-----------
H
H
local in CatchBlock
H
H 
---------------------------------------------
H
H
Finally-
H
H
-------
H
H
-
finally ------------------------
Block
-
-
H
H
-------
H
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
z.B. (siehe FalscherWuerfel, 11.1.2)
try
{freq[wurf=wuerfel(prob)]++;out.print(wurf);}
catch(RuntimeException e)
{out.print(e.getMessage());continue loop;}
finally
{if(wurf==0) {out.println(" Exception");}}
11.1.1 Auswerfen einer Ausnahme (throw)
Eine Ausnahme wird explizit mit einer throw-Anweisung (siehe
unten) aufgerufen oder es wird explizit eine Methode/Konstruktor
aufgerufen, die mit Throws (Syntax.083/089, Syntax.082) vereinbart
wurde und in der implizit eine throw-Anweisung aufgerufen wird.
Eine throw-Anweisung (ThrowStatement) ist nach Syntaxdiagramm
069 (für Statement, hier ein Auszug 069°) von der Form
069°:ThrowStatement ThrowAnweisung
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
H Throwable H
H
------------
H
H -
throw -
Expression
-
; -
H
H
------------
H
H H
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
z.B. (siehe FalscherWuerfel, 11.1.2)
throw new RuntimeException("not sum==1 ");}
Im Beispiel FalscherWuerfel (11.2) wird als "Benutzer-eigene
Ausnahme" RuntimeException ausgeworfen, einmal mit der message
"not in 0..1" und einmal mit der message "not sum==1 ".
Bei der Vereinbarung von Methoden/Konstruktoren (Syntax.083/089)
müssen alle in der Methode/Konstruktor ausgeworfenen Ausnahmen im
Throws-Teil der Vereinbarung (Syntax.082) aufgelistet werden.
z.B. (siehe FalscherWuerfel, 11.1.2) Throws
----------
----------
static int wuerfel(double[] prob) throws RuntimeException {...}
Das Auswerfen einer Ausnahme mit throw beendet abrupt alle im
Kontrollfluß noch offenen Konstrukte, z.B. eine noch nicht erreichte return-Anweisung, und übergibt die Kontrolle an das abfangende catch() (11.1.2) oder sonst weiter an die Methode uncaughtException() der ThreadGroup des gegenwärtigen Thread (12).
11.1.2 Abfangen einer Ausnahme (catch)
Im Catches-Teil einer try-Anweisung (11.1) müssen alle Ausnahmen abgefangen werden, die im try-Block explizit mit einer
throw-Anweisung (11.1.1) oder implizit in einer Methode/Konstruktor mit Throws (Syntax.083/089, Syntax.082) ausgeworfen wurden.
Der Typ des formalen Parameters von catch() bestimmt, welche Ausnahmen abgefangen werden.
----------------------------------
VVVVVVVVVVVVVVV
Abfangen der Ausnahmen (Prinzip)
VVVVVVVVVVVVV
VV
----------------------------------
VV
VV Es wird die mit throw ausgeworfene Ausnahme e mit catch() VV
VV abgefangen, deren Typ Klasse oder direkte oder indirekte VV
VV Subklasse des Typs T des formalen Parameters von catch() VV
VV ist, d.h. es gilt äquivalent dazu: e instanceof T == true VV
VV Bei mehreren in Frage kommenden catch() VV
VV fängt das erste in Schriftreihenfolge. VV
VV VV
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
Gemäß Hierarchie der Ausnahmen (Fig.11) können alle Non-Error
-Ausnahmen und nach Konvention auch alle Benutzer-eigenen Ausnahmen (11) abgefangen werden mit
catch(Exception e) {...} .
Aus der Klasse Throwable, die direkt oder indirekt Superklasse
aller Ausnahme-Klassen ist, erbt jede Ausnahme ein Datenfeld
String message, das beim Auswerfen per Konstruktor initialisiert
und beim Abfangen mit der in Throwable vereinbarten Methode
getMessage() abgefragt werden kann,
z.B. (siehe unten FalscherWuerfel)
catch(RuntimeException e) {err.print(e.toString());...}
//********************* FalscherWuerfel.java *********************
// 1000 Wuerfe. Wahrscheinlichkeiten vorgebbar. *
// Ausnahmebehandlung unstimmiger Vorgaben. *
// ConsoleInput.java muss im aktuellen Verzeichnis stehen *
//****************************************************************
// java.lang. Object
import static java.lang.System.*; // |`System
import static java.lang.Math .random; // `Math
class FalscherWuerfel extends ConsoleInput
{static int wuerfel(double[] prob) throws RuntimeException
{double sum=0;
for(int i=1;i<prob.length;i++)
{if (!(0<=prob[i] && prob[i]<=1))
{throw new RuntimeException("not in 0..1");}
sum+=prob[i];
}
if (!(1-1E-15<=sum && sum<=1+1E-15))
{throw new RuntimeException("not sum==1");}
sum=0;
double RAND=random();
for(int i=1;i<prob.length-1;i++)
if (RAND<(sum+=prob[i]))
{return i;}
return 6;
}
public static void main(String[] args) // args ungenutzt
{loop:for(;;)
{double[] prob=new double[7];
for(int i=1;i<prob.length;i++)
{out.print ("prob["+i+"]:");
prob[i]=getDouble();
}
int wurf=0;
int[] freq=new int[prob.length];
for(int cast=0;cast<1000;cast++)
try{ freq[wurf=wuerfel(prob)]++;//ggf.impl.throw
out.print (wurf);
}
catch(RuntimeException e)
{err.print (e.toString());
continue loop;
}
finally
{if (wurf==0)
{out.println(" !");}
}
for(int i=1;i<prob.length;i++)
{out.printf ("\nprob[%1$d]=%2$5.3f freq[%3$d]=%4$d",
i,1.0*freq[i]/1000,i,freq[i]);
}
break;
} } }
//****************************************************************
Output
Input
Output, random() abhaeng
Input
--------------------------
-----
-------------------------
-----
prob[1]:
.1
prob[1]:
.1
prob[2]:
.1
prob[2]:
.1
prob[3]:
.1
prob[3]:
.1
prob[4]:
.1
prob[4]:
.1
prob[5]:
.1
prob[5]:
.1
prob[6]:
2
prob[6]:
.5
..Exception: not in 0..1 !
3626366426 .. 3614616663
prob[1]:
.2
prob[1]=0,096 freq[1]=96
prob[2]:
.2
prob[2]=0,102 freq[2]=102
prob[3]:
.2
prob[3]=0,113 freq[3]=113
prob[4]:
.2
prob[4]=0,095 freq[4]=95
prob[5]:
.2
prob[5]=0,094 freq[5]=94
prob[6]:
.2
prob[6]=0,500 freq[6]=500
..Exception: not sum==1 !
Im obigen Programm FalscherWuerfel kann der Leser einen "verfälschten" (realen) Würfel durch Eingabe der Augen-Wahrscheinlichkeiten konstruieren.
Die Methode wuerfel(double[] prob) ist durch Ausnahmebehandlung
dagegen abgesichert, dass gegen Gesetze der Wahrscheinlichkeit
verstoßen wird, d.h. Werte außerhalb 0.0...1.0 auf prob[i] eingegeben werden oder eine prob-Summe ungleich 1.0 erreicht wird.
wuerfel() wirft an zwei Stellen Ausnahmen vom Typ RuntimeException aus mit jeweils verschieden initialisiertem String message.
Im Beispiel erscheint zuerst die message "not in 0..1" und dann
die message "not sum==1 ".
Im Sinne "fehlertoleranten Programmierens" wird das Programm
nach Abfangen dieser Ausnahmen nicht abgebrochen, sondern in der
for-Schleife mit ForEverControl (5.3.1) loop:for(;;) {...} mit
continue loop; so lange fortgeführt, bis fehlerfreie Eingabewerte
einen korrekten Programmablauf und danach einen Abbruch mit break;
ermöglichen. Nach 1000 Würfen nähert sich die Häufigkeit freq[]
der beobachteten Würfe geteilt durch 1000 den vorgegebenen erwarteten Wahrscheinlichkeiten prob[1],...,prob[6].
Um das Programm übersichtlich zu halten, wurde darauf verzichtet,
eine eigene Ausnahme-Klasse als Subklasse von RuntimeException zu
vereinbaren,
z.B.
class MyProbException extends RuntimeException
{ ProbException(String message){super(message);} }
11.1.3 Das letzte Wort behalten (finally)
Eine try-Anweisung kann mit einem finally-Konstrukt (11.1,
Syntax.069) abgeschlossen werden, das stets als letztes ausgeführt
wird, auch wenn der try-Block durch eine throw-Anweisung verlassen
und ein catch-Block ausgeführt oder durch eine throw-Anweisung
verlassen wurde.
Im Beispiel FalscherWuerfel (11.1.2) behält die initialisierte
Variable int wurf=0; im Falle einer Ausnahme ihren Anfangswert 0.
Dies wird im finally-Konstrukt abgefragt und dann " !" ausgedruckt:
finally {if(wurf==0) {out.println(" !");} }
zu Frage
abdeckbare Antwort
---------------------------------------------
--------------------
11 Wie ist die Hierarchie von Exception,
Throwable
RuntimeException, Error und Throwable?
Ecception' ` Error
`RuntimeException
11 Ist Ausnahmebehandlung möglich und
kann sich das Programm erholen von:
Behandlung Erholung
i.Allg.
Exception ?
ja ja
Error ?
ja nein
11.1.2 Was wird ausgedruckt?
System.out.print
true
(new RuntimeException()
instanceof Throwable);
11.1.1/2 Wie kann man den String message
catch(Exception e)
einer abgefangenen Ausnahme ausgeben
{put(e.getMessage())
und dann die Ausnahme wieder auswerfen?
;throw e;}
11.1.2 Wie kann man folgende Ausnahmen
abfangen:
Ganzzahl-Division durch 0 und
catch(ArithmeticEx-
Ganzzahl-Rest durch 0 ?
ception e) (4.3)
Alle Benutzer-eigenen Ausnahmen ?
catch(Exception e)
11.1.2 Wie kann man eine java.io.IOException
catch(Exception e)
abfangen, ohne java.io zu importieren?
(ConsoleInput, 8.2)
11.1.3 Kann man in einer try-Anweisung mit
nein, jeder throw
throw (explizit oder implizit in einer
muss abgefangen
Methode/Konstruktor mit Throws) auf
werden
Catches verzichten und dafür finally{}
setzen?