Java Standard: Exceptions


Was sind Ausnahmen?

Bearbeiten

Ausnahmen sind Unterbrechungen vom normalen, geplanten Programmfluss. In der Regel entstehen sie durch Fehler, die der Anwender oder der Programmierer gemacht hat. Auf Ausnahmen kann man reagieren, alternativ bricht das Programm in der Regel ab. Java stellt Sprachelemente bereit, mit denen Ausnahmen abgefangen und erzeugt werden können. Ferner sind Ausnahmen in der Umgebung von Java kategorisiert.

Grundstruktur der Ausnahmebehandlung

Bearbeiten

Es wird ein Block, der unter Umständen eine Ausnahme auslösen könnte, mit try { … } eingeschlossen. Anschließend folgt catch(AusnahmeTyp x) { … }. Hier wird die eigentliche Ausnahme aufgefangen. Es ist möglich, mehrere Ausnahmen in einem catch-Block unterzubringen. Ist die spezielle Ausnahme im catch-Parameter nicht aufgeführt, dann wird sie trotzdem behandelt: Entweder wird Programm abgebrochen, oder die Ausnahme wird weitergereicht. Diese Fallunterscheidung ist in der Praxis einfacher, als es sich liest:

void benutzeGangschaltung() {

  try {
    legeRückwärtsgangEin();  // kann "WirSindMitVollgasVorwärtsUnterwegsAusnahme" werfen
  }
  catch( WirSindMitVollgasVorwärtsUnterwegsAusnahme x) {
    bremseFahrzeugBisStillstand();
  }
}

Ohne try-und-catch könnte manches Schlimme passieren.

Mehrere Ausnahmen können folgendermaßen abgefangen werden:

void benutzeGangschaltung() {

  try {
    legeRückwärtsgangEin();  // kann "WirSindMitVollgasVorwärtsUnterwegsAusnahme" werfen
    beschleunigeAuto();      // kann "WirFahrenGegenParkendesAutoAusnahme" werfen
  }
  catch( WirSindMitVollgasVorwärtsUnterwegsAusnahme |  WirFahrenGegenParkendesAutoAusnahme x) {
    bremseFahrzeugBisStillstand();
  }
}

Ausnahmebehandlung lässt sich auch anders verschachteln, etwa hintereinander oder ineinander. Aber das Grundprinzip ist immer das Gleiche.

Umwandelausnahme

Bearbeiten
public class Testklasse {
  public static void main(String[] args) {
    Integer i = 0;

    try {
      i = new Integer("12");
    }
    catch( NumberFormatException e ) {
      System.err.println("Der String konnte leider nicht umgewandelt werden!" + e.getMessage() + i);
    }
    System.out.println(i);
  }
}

Das vorstehende Beispiel benutzt genau die einfache Grundstruktur. NumberFormatException wird geworfen, wenn die Konvertierung eines Wortes in eine Zahl Integer("12") nicht erfolgreich war. Das Objekt e stellt die Methode getMessage() bereit, mit der der Fehler weiter beschrieben werden kann.

Natürlich wird eine Ausnahme nur dann geworfen, wenn tatsächlich ein Fehler passiert. So ein Fehler ist hier gezeigt:

public class Testklasse {
  public static void main(String[] args) {
    Integer i = 0;

    try {
      i = new Integer("zwölf");
    }
    catch( NumberFormatException e ) {
      System.err.println("Der String konnte leider nicht umgewandelt werden!" + e.getMessage() + i);
    }
    System.out.println(i);
  }
}

Selber werfen

Bearbeiten

Ausnahmen können Sie selber werfen. Das folgende Programm zeigt, wie es geht. Jede Methode, die eine Ausnahme wirft, sollte mit throws Ausnahme1, Ausnahme2, …, AusnahmeN gekennzeichnet werden.

class Weitwurf {

  public static String Σ(String a, String b) throws NumberFormatException {

    java.lang.Integer i, j, summe;

    try {
      i = new Integer(a);
    } catch (NumberFormatException e) {
      System.err.println(a + " konnte nicht in eine ganze Zahl gewandelt werden. ");
      throw e;
    }

    try {
      j = new Integer(b);
    } catch (NumberFormatException e) {
      System.err.println(b + " konnte nicht in eine ganze Zahl gewandelt werden. ");
      throw e;
    }

    /* wenn wir hier sind, lief alles glatt */
    summe = i + j;                                      // Summe bilden
    String ergebnis = Integer.toString(summe, 10);      // String Zahl zur Basis 10
    return ergebnis;
  }

  public static void main(String[] args) {
    String summe = "0";
    try {
      summe = Σ("12", "13");
    } catch (NumberFormatException e) {
      System.err.println("Mindestens einer der Summanden lässt sich nicht in eine Zahl umwandeln.");
    }
    System.out.println(summe);
  }
}

Die Funktion Σ(String a, String b) bereichnet die Summe der übergebenen Argumente, dazu werden die Argumente in Zahlen konvertiert (Integer(a)), anschließend addiert und wieder in einen Text konvertiert (Integer.toString(summe, 10)). Wenigstens der erste Teil ist weniger konstruiert, als es auf den ersten Blick erscheint. Selbstverständlich können Sie ein Programm schreiben, das die Kommandozeilenargumente aufaddiert und als String −vielleicht sogar zur Basis 2− ausgibt, oder? Wenn bei der Konvertierung etwas schief läuft, dann wird in diesem Beispiel per throw e die Ausnahme einfach erneut ausgelöst und somit an die aufrufende Funktion main weitergereicht. Modifizieren Sie obenstehendes Beispiel doch so, dass tatsächlich eine Ausnahme geworfen wird.

Oft ist es notwendig, eine Aktion auch dann zu einem geordneten Ende zu bringen, wenn sie fehlschlägt. Wenn ich zum Beispiel eine Datei öffne, dann aber bei den Dateizugriffen ein Fehler auftritt, muss die Datei geschlossen werden, bevor das Programm weiter macht, weil sonst die Gefahr besteht, dass die komplette Datei verloren geht. Aber auch wenn die Aktion erfolgreich ausgeführt wird, muss die Datei geschlossen werden, aus dem selben Grund.

Um solche Notwendigkeiten sicherzustellen, gibt es das Schlüsselwort finally. Es beginnt den Block nach dem catch-Statement und enthält alle Programmbefehle, die in jedem Fall erledigt werden müssen, um die Aktion geordnet zu Ende zu führen. Er wird durchlaufen, egal, ob der catch-Block durchlaufen wurde oder ob nur der try-Block aktiviert worden war.

Im folgenden Beispiel wird eine Eingabe direkt von der Konsole (also normalerweise der Tastatur) gelesen, in einem String gespeichert und nach dem Drücken der Return-Taste auf dem Bildschirm ausgegeben. Dazu lenkt der Code den Stream von der Eingabe um. Vor dem Ende des Programmes muss der Stream auf jeden Fall wieder geschlossen werden, damit der Computer nicht lahmgelegt wird. Deswegen wird der Stream im finally-Block geschlossen, um sicherzustellen, dass diese Aktion auch dann ausgeführt wird, falls ein Fehler bei der Verarbeitung auftritt.

import java.io.*;

String text = "";

// Den Stream von der Systemkonsole umleiten
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));

// Eine eigene Eingabeaufforderung auf den Bildschirm schreiben
System.out.print("Bitte geben Sie einen Text ein: ");

try {
	// Bis zum Betätigen der Return-Taste die Eingaben von der Tastatur lesen
	text = console.readLine();
}
catch (IOExcepion e) {
	// Falls etwas schief geht: Den Benutzer informieren
	text="Fehler beim Lesen von Tastatureingaben!";
}
finally {
	// In jedem Fall die Systemeingabe zurück auf den normalen Weg stellen
	if (console != null) console.close();
}

// Das Ergebnis auf den Bildschirm schreiben
System.out.println(text);

Eigene Exceptions schreiben

Bearbeiten

Auch Exceptions sind Objekte, die allerdings als Nachricht genutzt werden. Dem entsprechend können sie auch von Programmierern erstellt werden. Das ist immer dann sinnvoll, wenn Sie eine Klasse schreiben, die andere Klassen über Probleme informieren muss und möglicherweise das komplette Programm beendet. Im Allgemeinen sind das Fehler, die während der Programmierung auftreten und unbedingt abgefangen werden müssen, um die Stabilität des Programmlaufes zu gewährleisten.

Um eine eigene Exception zu schreiben, müssen Sie nur eine Ableitung der Grundklasse Exception oder einer der davon abgeleiteten Klassen, zum Beispiel eine IOException, schreiben. Alles Weitere ist dann bereits funktionsfähig. Sinnvoll ist zusätzlich, eine erläuternde Nachricht zu übergeben, um die Stelle, an der die Exception ausgelöst wurde, möglichst genau zu erkennen. Eine solche eigene Exception könnte zum Beispiel folgendermaßen aussehen:

class MyException extends Exception {
	public MyException() {
		super();
	}

	public MyExtension(String message) {
		super(message);
	}
}

Es werden also nur die Konstruktoren benötigt (und davon auch nicht unbedingt alle), um eine eigene Exception zu erstellen; das obige Beispiel implementiert nur die beiden wichtigsten Konstruktoren. Diese Exception kann genauso genutzt werden wie jede andere Exception auch. Für die Übersichtlichkeit in der weiteren Abarbeitung ist es hilfreich, der Klasse einen aussagekräftigen Namen zu geben. Ein Name wie NullPointerException des Systems ist einprägsam und dokumentiert bereits bei der Programmerstellung, welcher Fehler im folgenden Codeblock abgefangen werden soll.

Auch wenn Sie mit dieser Form der eigenen Exception einen großen Teil aller auftretender Programmierprobleme sinnvoll abfangen können, bietet die Klasse zusätzliche Möglichkeiten, um Fehler zu suchen und zu beseitigen. Wir werden im Kapitel „Fortgeschrittene Programmierung“ darauf zurückkommen.