Eigene Ausnahmen

Ausnahmen selbst schreiben

  • Man legt eine Subklasse von Exception an
  • Man gibt ihr einen Namen, der auf „Exception“ endet
  • Man fügt Daten hinzu, die für die Ausnahme wichtig sind
  • Man bietet passende Konstruktoren zu den Daten an, mindestens aber zwei Konstruktoren:
    • MyException()
    • MyException(String message)

Man kann nicht nur eigene Exceptions schreiben, man sollte es auch. Zu einem guten API gehören auch passende Ausnahmen. Allerdings sollte man dies nicht übertreiben und so häufig wie möglich auch vorhandene Exceptions der Java-Klassenbibliothek wieder verwenden, da man hier dem Verwender des APIs bereits bekannte Ausnahmen wie z. B. IllegalArgumentException etc. anbieten kann.

Da bereits die Klasse Throwable das Serializable Interface implementiert (Details zu Serialisierung kommen später) muss man dafür sorgen, dass die selbstgeschriebenen Ausnahmen serialisierbar sind. Aus diesem Grund warnt Eclipse beim Anlegen einer neuen Exception, man solle eine serialVersionUID vergeben. Diese Warnung können wir an dieser Stelle noch nicht diskutieren.

Ausnahmen selbst werfen

  • Nur Subklassen von Throwable können geworfen werden
  • Im Allgemeinen wird man nur Runtime Exceptions und (Checked) Exceptions werfen
  • Errors sind der VM vorbehalten
  • Ausnahmen werden mit dem Schlüsselwort throw geworfen
  • Ausnahmen sollten frisch mit new erzeugt werden
  • Syntax: throw new WhatEverException(...)

Da Ausnahmen ganz normale Java-Objekte sind, muss man nur eine entsprechende Klasse schreiben, um eine eigene Art von Ausnahmen zu definieren. Die Ausnahme-Objekte werden, wie alle Objekte, mit new erzeugt.

Damit hat man aber erst ein Objekt erzeugt, es aber noch nicht auf den Weg gebracht. Hierzu muss man es mit dem Schlüsselwort throw werfen (Werfen von Ausnahmen).

Da Errors für die VM reserviert sind, sollten Sie ausschließlich Runtime- und Checked-Exceptions selbst schreiben und werfen. D. h., dass Sie nicht direkt von Throwable ableiten, sondern immer von Exception oder RuntimeException.

Beispiel: Exceptions werfen

try {
    System.out.println("A");

    if (true) {
        throw new IOException("Festplatte abgebrannt");
    }

    System.out.println("B");
}
catch (IOException e) {
    System.out.println("Fehler: " + e.getMessage());
}
A
Fehler: Festplatte abgebrannt

Das if (true) ist an dieser Stelle tatsächlich nötig, weil der Compiler sonst die Zeile System.out.println("B") als nicht erreichbaren Code (unreachable Code) gemeldet hätte.

Das Beispiel zeigt, dass man eine Ausnahme direkt nach dem Werfen mit throw wieder einfangen kann.

public void method() {
    try {
        System.out.println("A");
        werfer();
        System.out.println("B");
    }
    catch (IOException e) {
        System.out.println("Fehler: " + e.getMessage());
    }
}

private void werfer() throws IOException {
    throw new IOException("Festplatte explodiert");
}
A
Fehler: Festplatte explodiert

Im Beispiel ist eine Methode werfer zu sehen, die mit throw eine Ausnahme vom Typ IOException wirft und auch korrekt über throws deklariert. Die Methode method ruft werfer auf und fängt die Exception sofort mit einem catch. Die Ausgabe des Programms ist damit „A“ und dann die Nachricht aus der Ausnahme. Zur Ausgabe „B“ kommt es nicht mehr, weil vorher die Ausnahme auftritt und der try-Block abrupt beendet wird.

Beispiel: Die Exception

public class ServerException extends Exception {
    private int port;
    private String host;

    public ServerException(String message, String host, int port) {
        super(message);
        this.host = host;
        this.port = port;
    }

    public int getPort() {
        return port;
    }

    public String getHost() {
        return host;
    }
}

Man kann nicht nur vorhandene Ausnahmen (z. B. IOException) werfen, sondern auch eigene Ausnahmen schreiben. Das Beispiel zeigt eine selbstgeschriebene Ausnahme mit dem Namen ServerException. Diese enthält weitere Daten zum Problem, das die Ausnahme ausgelöst hat. Hier sind dies Informationen zu einer Netzwerkverbindung, die nicht aufgebaut werden konnte. port und host bezeichnen einen Server im Internet eindeutig.

Auf die übliche Vorgehensweise, bei einer Exception mindestens einen Default-Konstruktor und einen mit einer Nachricht anzubieten, wurde hier aus Platzgründen verzichtet. Folgende Konstruktoren sollten noch zur ServerException hinzugefügt werden:

  • ServerException()
  • ServerException(String message)
  • ServerException(Throwable cause)
  • ServerException(String message, Throwable cause)

Beispiel: Der Werfer

public class Server {
    public static void connect(String host, int port)
            throws ServerException {

        int result = connectInternal(host, port);

        if (result == -1) {
            throw new ServerException("Cannot connect to "
                + host + ":" + port, host, port);
        }

        // ...

    }

    private static native int connectInternal(String host, int port);
}

Die Klasse Server symbolisiert eine Komponente, die Netzwerkverbindungen aufbauen kann. Die eigentliche Verbindung wird von der Methode connectInternal hergestellt, die nativ, d. h. nicht in Java, sondern in C implementiert ist. Aus diesem Grund kann connectInternal keine Ausnahmen werfen, sondern zeigt ein Verbindungsproblem durch einen Rückgabewert von -1 an.

Die Methode connect ruft die interne Methode auf und wirft im Fehlerfall eine Ausnahme vom Typ ServerException. Neben der menschen-lesbaren Informationen „Cannot connect to“ werden noch die entsprechenden Attribute host und port der Ausnahme gesetzt.

Beispiel: Der Fänger

public class Client {
    public void doIt() {

        try {
            Server.connect("server1", 22);
        } catch (ServerException e1) {
            System.err.println("Keine Verbindung zum Server "
                    + e1.getHost() + " auf Port " + e1.getPort());

            try {
                Server.connect("server2", 22);
            } catch (ServerException e2) {
                System.err.println("Ersatzserver geht auch nicht: "
                        + e2.getHost() + " auf Port " + e2.getPort());
            }
        }
    }
}

Die Klasse Client versucht eine Verbindung mithilfe der Klasse Server aufzubauen. Gelingt dies nicht, wird ein Ersatzserver versucht.

Man erkennt an diesem Beispiel zwei Dinge:

  • try-catch-Blöcke können ineinander geschachtelt werden
  • Informationen aus der Ausnahme können ausgewertet werden. Hier wird insbesondere nicht die englische Nachricht aus der Ausnahme verwendet, sondern Client übersetzt die Meldung unter Verwendung der Attribute der Ausnahme.

Copyright © 2025 Thomas Smits