9 Errors und Exceptions

Eines der Grundkonzepte von Java ist Sicherheit und Stabilität. Um dies zu gewährleisten braucht man die Möglichkeit auf Fehler, die nur zur Laufzeit eines Programms möglich sind, zu reagieren. In Java werden solche Fehler in Errors und Exceptions umgewandelt. Alle Errors und Exceptions sind von der Klasse java.lang.Throwable abgeleitet. Sie können vom Programmierer abgefangen und bearbeitet oder auch weitergeleitet werden. Eine Exception, welche immer weitergeben wird, wird spätestens von der JVM abgefangen und von dieser eine Fehlermeldung auf der Konsole generiert.

9.1 Die Klasse Throwable

Aller Errors und Exceptions müssen von der Klasse java.lang.Throwable abgeleitet sein, damit die Fehlerbehandlung der JVM funktionieren kann.

9.1.1 Die Konstruktoren

9.1.1.1 Throwable(String message)

Diesem Konstruktor wird eine individuelle Beschreibung des Fehlers mitgegeben. Bei eigenen Exceptions sollte man immer eine solche Beschreibung hinzufügen.

9.1.1.2 Throwable()

Wird dieser Konstruktor aufgerufen, so wird der Fehlerbeschreibung der Wert null zugewiesen.

9.1.2 Die Methode

In diesem Kapitel werden nicht alle Methoden der Klasse Throwable beschrieben, sondern nur die meiner Meinung nach wichtigsten. Eine vollständige Beschreibung aller Methoden findet sich in der Java-Dokumentation.

9.1.2.1 getMessage

public String getMessage() gibt die Fehlerbeschreibung an die aufrufende Methode zurück.

9.1.2.2 printStackTrace

Diese Methoden geben genaue Informationen darüber aus, in welcher Methode und Klasse der zugehörige Fehler aufgetreten ist. Als weitere Information werden auch die aufrufenden Methoden ausgegeben. Ist der JVM auch der Sourcecode der Klassen bekannt, so wird auch noch die Zeile im Sourcecode angegeben, an der der Fehler aufgetreten ist. Bei der ersten Methode wird die Information auf Standardout ausgegeben. Mit beiden anderen Methoden kann man das Ziel der Ausgabe selber definieren. Dies kann man sich vor allem zur Ausgabe der Fehler in ein Logfile nützlich machen. printStackTrace wird von der JVM dazu benutzt, um Informationen über nicht abgefangene Fehler auf der Konsole auszugeben.

9.1.2.3 toString

public String toString() gibt eine Kurzbeschreibung des Fehlers an die aufrufende Methode zurück. Diese besteht standardmässig aus dem Namen der Exception und der Fehlerbeschreibung.

9.2 Exception

Die Klasse java.lang.Exception ist die Superklasse aller Exceptions. Mit Ausnahme der Klasse java.lang.RuntimeException und ihrer Subklassen müssen alle Exceptions vom Programmierer abgefangen und bearbeitet werden. Das Abfangen wird bereits vom Compiler überprüft.

9.3 RuntimeException

Die RuntimeExceptions stellen eine Besonderheit dar. Dies sind Laufzeitfehler, die nicht unbedingt abgefangen werden müssen. Sie werden aber auf jeden Fall von der JVM abgefangen, welche eine entsprechende Fehlermeldung auf der Konsole ausgibt. Diese Exceptions sind alle von der Klasse java.lang.RuntimeException abgeleitet. Die wohl am meisten auftretenden RuntimeExceptions sind die java.lang.NullPointerException und die java.lang.ArrayIndexOutOfBoundsException.

9.4 Abfangen einer Exception

Um eine Exception abfangen zu können, wird der Programmteil in dem sie auftreten kann, in einen try-Block geschrieben. Die darin auftretende Exception wird mit einer catch-Anweisung, die an den try-Block anschliesst, abgefangen.

  try {
    ...
    ExceptionAnweisung;
    ...
  } catch(XYException xyEx) {
    ...
    ...
  }

Vom Compiler wird überprüft, ob die im catch-Block angegebene Exception im vorangestellten try-Block überhaupt auftreten kann. Tritt zur Laufzeit des Programms bei der Anweisung, welche eine Exception auslösen kann, eine Exception auf, so wird der try-Anweisungsblock an dieser Stelle abgebrochen und der Anweisungsblock der zugehörigen catch-Definition ausgeführt. Die Angaben welche Methoden welche Exceptions auslösen können, findet man in der Dokumentation. Vergisst man das Abfangen einer Exception, so wird ein Compilerfehler ausglöst.

import java.text.DecimalFormat;
import java.text.ParseException;

public class NumberParser1 {
  public NumberParser1(String number) {
    DecimalFormat numberFormat = new DecimalFormat();
    try {
      Number xNumber = numberFormat.parse(number);
      double x = xNumber.doubleValue();
      System.out.println("Die eingegebene Zahl =  " + x);
    } catch(ParseException pEx) {
      System.out.println("Der eingegebene Parameter konnte nicht in eine Zahl umgewandelt werden.");
    }
  }

  public static void main(String[] args) {
    try {
      new NumberParser1(args[0]);
    } catch(ArrayIndexOutOfBoundsException aiobEx) {
      System.out.println("Das Programm erwartet eine Zahl als Eingabeparameter.");
    }
  }
}
Sourcefile

9.4.1 Abfangen mehrerer Exceptions

Können in einem try-Block mehrere Exceptions ausgelöst werden, so müssen diese alle abgefangen werden.

  try {
    ...
    ExceptionAnweisung1;
    ...
    ExceptionAnweisung2;
    ...
  } catch(Exception1 ex1) {
    ...
  } catch(Exception2 ex2) {
    ...
  } catch(...) {
    ...
  } catch(ExceptionN exN) {
    ...
  }

Für jede mögliche Exception kann an den try-Block ein catch-Block angehängt werden. So kann auf jede Excception einzeln reagiert werden. Es muss aber darauf geachtet werden, dass nicht z.B. Exception2 eine Subklasse von Exception1 ist. In diesem Falle würde der erste catch-Block schon alle Exceptions vom Typ Exception2 abfangen, da diese durch die Vererbung ja auch vom Typ Exception1 sind. Der Compiler gibt in diesem Fall eine entsprechende Fehlermeldung aus. Soll nicht jede Exception einzeln behandelt werden, so kann auch eine gemeinsame Superklasse angegeben werden.

  Exception4, Exception5 sind Subklasse von Exception1
  Exception6, Exception7 sind Subklasse von Exception2
  Exception8, Exception9 sind Subklasse von Exception3

  try {
    ...
    ExceptionAnweisung;
    ...
  } catch(Exception1 ex1) {
    // Hier werden Exceptions vom Typ Exception1, Exception4, Exception5 behandelt.
    ...
  } catch(Exception2 ex2) {
     // Hier werden Exceptions vom Typ Exception2, Exception6, Exception7 behandelt.
   ...
  } catch(Exception3 ex3) {
     // Hier werden Exceptions vom Typ Exception3, Exception8, Exception9 behandelt.
   ...
  }

In einem catch-Block können auch die Klassen java.lang.Exception oder java.lang.Throwable abgefangen werden. Damit muss auf jeden Fall nur ein einziger catch-Block angegeben werden. Solche generellen catch-Blöcke müssen aber sehr vorsichtig eingesetzt werden, da hier zu Teil auch Exceptions mit abgefangen werden, die man gerne in einem übergeordneten Block behandlen möchte.

9.5 Weiterleiten einer Exception

In manchen Fällen kann es aber auch sinnvoll sein Exceptions nicht direkt abzuarbeiten, sondern sie an die aufrufende Methode weiterzuleiten. In diesem Falle muss an die Methoden Deklaration eine throws-Klausel mit den entsprechenden Exceptions angehängt werden. Die Exception muss dann im aufrufenden Anweisungsblock behandelt oder ebenfalls weitergegeben werden.

  public returnTyp MethodenName throws ExceptionXY {
    ...
    ExceptionAnweisung;
    ...
  }
import java.text.DecimalFormat;
import java.text.ParseException;

public class NumberParser2 {
  public NumberParser2(String number) throws ParseException {
    DecimalFormat numberFormat = new DecimalFormat();
    Number xNumber = numberFormat.parse(number);
    double x = xNumber.doubleValue();
    System.out.println("Die eingegebene Zahl =  " + x);
  }

  public static void main(String[] args) {
    try {
      new NumberParser2(args[0]);
    } catch(ArrayIndexOutOfBoundsException aiobEx) {
      System.out.println("Das Programm erwartet eine Zahl als Eingabeparameter.");
    } catch(ParseException pEx) {
      System.out.println("Der eingegebene Parameter konnte nicht in eine Zahl" +
      "verwandelt werden.");
    }
  }
}
Sourcefile

9.6 Die 'finally'-Anweisung

In einigen Fällen muss man sicherstellen, dass Teile des Programms auf jeden Fall ausgeführt werden, egal ob eine Exception aufgetreten ist. Diesen Fall kann man mit einem optionalen finally-Block nach den catch-Anweisungen realisieren.

  try {
    ...
    ExceptionAnweisung;
    ...
  } catch(XYException xyEx) {
    ...
    ...
  } finally {
    // Die Anweisungen in diesem Block werden auf jeden Fall ausgeführt.
    ...
  }

Ein finally-Block sollte auf jeden Fall immer dann angefügt werden, wenn man auf eine Datei, eine Datenbank oder auf eine Netzwerkverbindung zugegriffen hat, um diese auch bei Auftreten eines Fehlers zu schliessen. Die Anweisungen eines finally-Blockes werden auch dann ausgeführt, wenn der try oder catch-Block mit einer return-Anweisung beendet wird.

  try {
    ...
    ExceptionAnweisung;
    ...
    return tryRückgabeWert;
  } catch(XYException xyEx) {
    ...
     return catchRückgabeWert;
  } finally {
    // Die Anweisungen in diesem Block werden auf jeden Fall ausgeführt.
    ...
    return finallyRückgabeWert;
  }

Achtung!!! Enthält der finally-Block eine return-Anweisung, so wird immer diese ausgeführt. Das bedeutet, dass im obigen Fall immer der finallyRückgabeWert an die aufrufende Methode zurückgegeben wird.

9.6.1 finally ohne catch

Eine finally-Anweisung kann auch ohne einen catch-Block verwendet werden. Es muss aber auf jeden Fall ein try-Block vorhanden sein. Dies kann man z.B. verwenden, wenn bei Verlassen einer Methode aus einer bedingten Anweisung heraus, einige Anweisungen auf jeden Fall noch ausgeführt werden müssen.

  try {
    ...
    switch() {
      case constant1:
                             ...
        return rückgabeWert1;
      case constant2:
                             ...
        return rückgabeWert2;
      ...
      ...
      case constantN:
                             ...
        return rückgabeWertN;
  } finally {
    // Die Anweisungen in diesem Block werden auf jeden Fall ausgeführt.
    ...
  }

9.7 Auslösen einer Exception

Eine Exception wird mit dem Schlüsselwort throw ausgelöst. Dem Aufruf von throw wird eine Instanz der auszulösenden Exception mitgegeben. Bei einem Konstruktor oder einer Methode, in der eine Exception ausgelöst wird, muss diese mit throws in der Definition des Konstruktors oder der Methode angegeben werden.

  public returnTyp MethodenName throws ExceptionXY {
    ...
    if(bedingung) {
      throw new ExceptionXY();
    }
    ...
  }

Die Abarbeitung der Methode wird an der Stelle, an der die Exception geworfen wird, abgebrochen.

import java.text.ParseException;

public class NumberParser3 {
  public NumberParser3(String number) throws ParseException {
    try {
      Double d = new Double(number);
    } catch(NumberFormatException nfEx) {
      throw new ParseException(nfEx.getMessage(), 0);
    }
    double x = xNumber.doubleValue();
    System.out.println("Die eingegebene Zahl =  " + x);
  }

  public static void main(String[] args) {
    try {
      new NumberParser2(args[0]);
    } catch(ArrayIndexOutOfBoundsException aiobEx) {
      System.out.println("Das Programm erwartet eine Zahl als Eingabeparameter.");
    } catch(ParseException pEx) {
      System.out.println(
        "Der eingegebene Parameter konnte nicht in eine Zahl" +
        "verwandelt werden."
      );
    }
  }
}

9.7.1 Auslösen von Exceptions in überschriebenen Methoden

Will man in einer überschriebenen Methode eine Exception auslösen, so muss diese Exception von der gleichen Klasse oder einer Subklasse der in der Originalmethode angegebenen Exception sein.

  class SuperKlasse {
    public returnTyp MethodenName throws ExceptionXY {
      ...
      if(bedingung) {
        throw new ExceptionXY();
      }
      ...
    }
  }

  class SubKlasse extends SuperKlasse {
    public returnTyp MethodenName throws ExceptionAB {
      // Die Angabe von ExceptionAB in der throws-Klausel
      // ist nur dann erlaubt, wenn ExceptionAB Subklasse von ExceptionXY
      ...
      if(bedingung) {
        throw new ExceptionAB();
      }
      ...
    }
  }

9.8 Selbstdefinierte Exceptions

Jede selbstdefinierte Exception muss von der Klasse java.lang.Throwable oder einer ihrer Subklassen abgeleitet sein. In den meisten Fällen wird man als Superklasse entweder java.lang.Exception oder java.lang.RuntimeException verwenden.

  class MyException extends Exception {
    MyException(Parameterliste) {
      ...
    }
  }

9.9 Errors

Errors sind in der Regel System bedingte Laufzeitfehler. Ein Beispiel dafür ist der java.lang.OutOfMemoryError. In den meisten Fällen ist ein fortführen des Programms nicht mehr sinnvoll. Trotzdem sollte man versuchen auch diese Fehler abzufangen und eventuell auch versuchen noch nicht gespeicherte Daten des Benutzers zu sichern. Danach sollte das Programm aber beendet und neu gestartet werden, bevor weiter gearbeitet wird.

Created with Vim Zurück   Weiter