Java Programmierung

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  v             local in CatchBlock              |   H
      H  |<---------------------------------------------|   H    
      H  |                                     Finally- |   H
      H  |                                    ,-------, v   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(" !");} }







11.2     Testfragen

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?                                |
                                             |       
    Java Programmierung
  Kap.12