Java Exception Behandlung

Die Verwendung und Behandlung von Exceptions wird teilweise sehr kontrovers diskutiert. Das dürfte daran liegen, dass man aufgrund der Vielzahl unterschiedlicher Software-Aufgabenstellungen nur sehr bedingt allgemein gültige Regeln dazu aufstellen kann und des Weiteren jeder seinen eigenen Programmierstil bezüglich des Umgangs mit Ausnahmebedingungen pflegt.

Ausnahmen sind die Ausnahme

Exceptions sind, wie der Name schon sagt, Ausnahmen und sollten auf keinen Fall dazu verwendet werden, den normalen Programmfluss wie im folgenden Beispiel zu steuern:

public class ExceptionExample1 
{ public static void main(String[] args) 
  { String[] sArray = { "a", "b", "c" };
    try // missbräuchliche Verwendung eine Exception
    { int i = 0; 
      while (true) System.out.println(sArray[i++]);
    }
    catch (Exception ex)
    { System.out.println ("Loop Ende");
    }
  }
}
C:\temp>java ExceptionExample1
a
b
c
Loop Ende

Anders sieht es beispielsweise aus, wenn die Schnittstellenvereinbarung definiert, dass nur die Werte "a", "b" oder "c" übergeben werden dürfen, und es wird der Wert "d" übergeben. In diesem Fall handelt es sich um eine echte Abweichung von der Norm, die die Verwendung einer Exception rechtfertigt.

Für wen sind Ausnahmen gedacht?

Ausnahmen fallen aus dem normalen Programmablauf heraus und fordern, soweit sie nicht von der Programmlogik zu korrigieren sind, eine Reaktion, die außerhalb der Zuständigkeit der entsprechenden Software liegt. Sie sind also in der Regel dazu gedacht, den Anwender mit sinnvollen Informationen zu versorgen, damit er durch geeignete Maßnahmen seine Arbeit nach Behebung der zugrunde liegenden Ursache fortsetzen kann. Anwender einer Software ist derjenige, der das Programm gestartet hat (Sachbearbeiter / Administrator) und ggf. der Betriebsverantwortliche.

Handelt es sich bei der Software um eine API, ist der Anwender der Softwareentwickler, der diese API nutzt und der entweder bei Auftreten einer Ausnahme seine Schnittstellenmethoden modifiziert oder aber entsprechende Ausnahme-Informationen aufbereitet an den End-User seiner Anwendung weiter gibt.

Bei der Entwicklung umfangreicherer Softwareprojekte habe ich selbst die Erfahrung gemacht, dass es am sinnvollsten ist, Ausnahmen, die nicht unmittelbar von der Software selbst behoben werden können, erst mal weiter ganz nach oben zu werfen. Oben bedeutet eine zentrale Ausgaberoutine, die in einer Batchanwendung den Fehler in Logfiles protokolliert und bei einer Dialoganwendung einen Fehlerdialog öffnet. Im Lauf der Fertigstellung der Software finden sich dann die Programmabschnitte,

  • bei denen Ausnahmen durch Programmierfehler hervorgerufen wurden und die nach Behebung der Fehler keine weitere Aufmerksamkeit erfordern,
  • die geeignet sind, um Ausnahmen durch geeignete Programmlogik zu beheben,
  • die geeignet sind, um Ausnahmen für den Anwender weiter aufzubereiten, beispielsweise um beim Auftreten einer SocketException eine Exception mit dem Hinweis, dass das Netzwerk oder der Proxy nicht erreichbar sind, zu generieren,
  • die geeignet sind, um dem Anwender die Informationen zu einer Ausnahme zu präsentieren.

Nicht empfehlenswert ist meines Erachtens, wenn jede Methode auf dem Ausnahme-Stack ihren eigenen "Senf" dazu gibt oder eigene Exceptions mit eigener Interpretation generiert, da in diesem Fall das Programm mehr mit Ausnahmen als mit der Regel beschäftigt ist und äußerst unübersichtlich wird.

Welche Ausnahmen verwenden?

Java unterscheidet zwischen zwei Arten von Ausnahmen:

  1. unchecked Exceptions, die sich von java.lang.RuntimeException ableiten und die nicht explizit mit einem try {} catch {} Block zu behandeln sind, und
  2. allen anderen von java.lang.Exception abgeleiteten Exceptions, die explizit entweder durch einen
    try {} catch {} Block zu behandeln sind oder aber deren potentiell betroffene Methode eine entsprechende throws Klausel enthält.

Als grobe Faustregel, wann welche Exceptions zu verwenden sind, sollten unchecked Exceptions verwendet werden, wenn in Folge eines Programmfehlers keine sinnvolle Behandlung möglich ist und checked Exceptions für alle anderen Ausnahmen. Bedenken sollte man dabei allerdings, dass checked Exceptions wegen ihres Overheads nur sehr sparsam eingesetzt werden sollten.

Auch sollte man nicht für jedes neue Problem eigene Exceptions implementieren, sondern auf bereits im Java Sprachumfang vorhandene Klassen zurückgreifen wie beispielsweise:

  • java.lang.IllegalArgumentException
    wenn eine Methode mit unvereinbarten Werten aufgerufen wird, oder
  • java.lang.IllegalStateException
    wenn ein Objekt derzeit nicht in der Lage ist, einen Aufruf zu bearbeiten.

Informationen zu einer Exception

Jede Exception sollte mit einem aussagekräftig beschriebenen Nachrichtentext versehen werden. Das ist leider nicht immer der Fall und man sollte sich deshalb auch nicht darauf verlassen. So wird vom Java Laufzeitsystem gern mal eine NullPointerException geworfen, deren Message NULL ist und wenn man diese dann nicht richtig auswertet bekomm man - was wohl? - eine NullPointerException und dann ist "Schicht im Schacht"!

Folgendes Beispiel demonstriert, wie man an welche Informationen einer Exception herankommen kann:

public class ExceptionExample2 
{  public static void main(String[] args) 
  { int x = 0, y = 1;
    try 
    { System.out.println(y / x);
    }
    catch (Exception ex)
    { System.out.println("Error: " + ex.getMessage());
      StackTraceElement stackElements[] = ex.getStackTrace();
      // stack[0] enthält die Methode, die die Exception geworfen hat.
      for (int i = 0; i < stackElements.length; i++) 
      { System.out.println("Stack[" + i + "]");
        String filename = stackElements[i].getFileName();
        if (filename != null) 
        { System.out.println("  Filename    : " + filename);
        }
        System.out.println("  Classname   : " + stackElements[i].getClassName());
        System.out.println("  MethodName  : " + stackElements[i].getMethodName());
        System.out.println("  NativeMethod: " + stackElements[i].isNativeMethod());
        System.out.println("  Line-Number : " + stackElements[i].getLineNumber());
      }
    }
  }
}
C:\temp>java ExceptionExample2
Error: / by zero
Stack[0]
  Filename    : ExceptionExample2.java
  Classname   : ExceptionExample2
  MethodName  : main
  NativeMethod: false
  Line-Number : 5