Was ist eine Exception?

Aus Byte-Welt Wiki
Wechseln zu: Navigation, Suche
Dieser Artikel überschneidet sich inhaltlich teilweise mit Exception (Java)

Eine Exception ist eine Ausnahmesituation, die normalerweise durch Fehler hervorgerufen wird. Diese Ausnahmen führen in der Regel zu einer Änderung des Programmablaufs, um etwa den Fehler zu beheben oder das Programm in einen sicheren Zustand zu bringen.


Was ist der Unterschied zwischen checked und unchecked Exceptions?

Unchecked Exceptions sind von der Klasse RuntimeException oder einer deren Unterklassen abgeleitet. Sie müssen nicht explizit behandelt (mit catch gefangen) oder mit throws weitergeworfen werden.

Checked Exceptions sind nicht von RuntimeException abgeleitet. Kann eine Methode eine checked Exception werfen, so muss sie entweder behandelt werden (try-catch) oder die aufrufende Methode muss sie in der throws-Klausel deklarieren. D. h. die Exception wird weiter geworfen.

Die Begriffe Runtime-Exception und unchecked Exception können synonym verwendet werden.

public void methodeMitRuntimeException() {
    throw new NullPointerException();    // NullPointerException ist eine RuntimeException
}
 
public void methodeMitCheckedExceptionBehandelt() {
    try {
         foo(); // foo() kann IOException werfen. IOException ist eine checked Exception.
    }
    catch(IOException ex) {
         //... Exception behandeln
    }
}
 
public void methodeMitCheckedExceptionWeiterwerfen() throws IOException {    
    foo(); // foo() kann IOException werfen. IOException ist eine checked Exception.
}


Wie behandle ich eine Exception richtig?

Eine Exception sollte auf keinen Fall einfach verschluckt werden. Eine Fehlerausgabe, Logging der Exception oder ein Weiterwerfen sollten vorhanden sein. Auch ist davon abzuraten, generell alle Exceptions in einem catch abzufangen. Es sollten immer nur konkrete Ausnahmen gefangen werden. Dies verhindert, dass man "aus Versehen" eine Exception fängt, die gar nicht behandelt werden soll.

public void foo() {
    try {
         methodeDieExceptionsWirft();
    }
    catch(Exception x) {    // FALSCH! Nicht alle Exceptions auf einmal fangen! 
                            // FALSCH! Exceptions nicht ignorieren!
    }
}
 
public void bar() {
    try {
         methodeDieExceptionsWirft();
    }
    catch(FileNotFoundException fnfex) {            // RICHTIG: konkrete Exceptions einzeln angeben
         myExceptionHandler.handleException(x);    
    }
    catch(SQLException sqlex) {                     
         sqlex.printStacktrace();                   // OK: Stacktrace auf Konsole ausgeben
         LOGGER.error(sqlex);                       // BESSER: Exception loggen
    }
    catch(ParseException pex) {
         throw new RuntimeException("unerwartete Exception", pex);  // OK: checked Exception in RTEx kapseln
    }
}


Wann soll ich Runtime-Exceptions und wann checked Exceptions verwenden?

Checked Exceptions zwingen den Aufrufer zur sofortigen Behandlung bzw. expliziten Weiterleitung der Exception. Dies ist nur sinnvoll, wenn eine Chance besteht, die Ausnahmesituation zu reparieren ("reasonably be expected to recover" [1]). Andernfalls gibt es nur die Möglichkeit, dem Anwender eine Fehlermeldung zu präsentieren, die Exception zu loggen oder die Anwendung kontrolliert zu beenden. In solchen Fällen sind ungeprüfte Ausnahmen (Runtime-Exceptions) vorzuziehen.

Unchecked Exceptions — The Controversy (The Java™ Tutorials > Essential Classes > Exceptions)


Stimmt es, dass RuntimeExceptions immer auf Programmierfehler hindeuten und nie gefangen werden sollten?

Ursprünglich stimmte das, wenn man nur die vordefinierten Exception in der Java API betrachtet. Ausnahmen wie NullPointerException, ArrayIndexOutOfBoundsException oder IllegalArgumentException deuten wirklich auf Programmierfehler hin, die in funktionierenden Programmen nie auftreten sollten. Falls doch, sollte der Bug behoben werden, statt die Exception zu behandeln.

Andererseits gehen Frameworks und Software-Bibliotheken mehr und mehr dazu über, nur noch unchecked Exceptions zu benutzen. Diese sind in der Regel nicht durch Programmierfehler bedingt. So wirft das Persistenz-Framework Hibernate eine RuntimeException, wenn beispielsweise die Datenbank nicht verfügbar ist. Solche Ausnahmen können natürlich auch gefangen und behandelt werden, wenn dies möglich und sinnvoll ist.

Eine Gleichsetzung Runtime-Exceptions == Programmierfehler kann also nicht verallgemeinert werden.


Was ist das Problem mit checked Exceptions?

Checked Exceptions sind nur dann sinnvoll, wenn bei ihrem Auftreten unmittelbar die Möglichkeit besteht, die Ausnahmesituation zu beheben und dem Programmablauf normal fortzufahren.

- Checked Exceptions führen zu überflüssigem Code

In der Praxis sind Fälle, in denen man auf jeden Fall von einer Behebbarkeit ausgehen kann, jedoch sehr selten. Trotzdem wird er Entwickler gezwungen, die Exception (unnötigerweise) zu behandeln, da sonst der Code nicht kompilierbar ist. Da führt zu eigentlich überflüssigen try-catch-Anweisungen, die den Quelltext länger und schwerer zu lesen machen. Im schlimmsten Fall wird die Exception einfach nur verschluckt.

- Behebbare Ausnahmen sind selten und oft nicht eindeutig zu identifizieren

Beispiel: IOException auf der Java Standard-API ist eine checked Exception, sie muss also behandelt werden. In einigen Anwendungsfällen ist dies natürlich möglich. Hat der Anwender beispielsweise einen falschen Dateinamen eingetippt, so kann man den entsprechenden Eingabedialog erneut öffnen und den richtigen Dateinamen verlangen. Diese Situation ist behebbar. Was soll aber passieren, wenn IOException in einem Server-Prozess fliegt, etwa, weil die Konfigurationsdatei der Servers nicht gelesen werden kann oder kein freier Festplattenspeicher mehr verfügbar ist. Diese Situationen sind nicht behebbar. Es bleibt einem nichts anderes übrig, als die Exception zu loggen, ggf. den Administrator zu verständigen und das Programm zu beenden. IOException kann also nicht immer sinnvoll behandelt werden. Daraus folgt, dass sie eigentlich keine checked Exception sein sollte, sondern eine Runtime-Exception.

Generell sollten Frameworks oder allgemein verwendbare Bibliotheken keine checked Exceptions verwenden. In abgeschlossenen Software-Systemen (Individualsoftware) können geprüfte Ausnahmen manchmal sinnvoll sein.

- Checked Exceptions verschmutzen Interfaces und führen zu hoher Kopplung

Beispiel: Das Service-Interface von Java RMI verlangt von jeder Methode, die checked Exception RemoteException in der throws-Klausel zu deklarieren. Dadurch wird es hart an diese Technologie gekoppelt. Soll zum Beispiel in Zukunft RMI durch ein anderes Remote-Procedure-Call Produkt ausgetauscht werden, sind umfangreiche Änderungen am Quelltext notwendig, um überall die RemoteExceptions und die entsprechenden catch-Blöcke zu entfernen. Umgekehrt ist es noch schlimmer: Besteht bereits ein Service-Interface, das im Programm verwendet wird, und soll es jetzt durch RMI implementiert werden, so muss man entsprechend throws RemoteException an allen Methoden hinzufügen. An sämtlichen aufrufenden Stellen muss darüber hinaus die Exception-Behandlung ergänzt werden.

Warum dann überhaupt checked Exceptions?

Gute Frage. Der Nutzen von checked Exceptions hat sich als sehr begrenzt heraus gestellt. Die Nachteile durch das (gezwungene) Behandeln (mehr Quelltext, eventuell Verschlucken der Exception, siehe oben), können Probleme verursachen. Es gibt einen Trend weg von checked Exceptions, z.B. werden in Hibernate und Spring-Framework nur noch Runtime-Exceptions verwendet. Das Konzept der checked Exceptions gibt es so nur in Java. In neueren, auch von Java inspirierten Sprachen wie C# oder Groovy, wurde darauf verzichtet.

Was tun mit all den unbehandelten Exceptions?

RuntimeExceptions die nicht mit catch gefangen und behandelt werden, nennt man uncaught Exceptions. Seit Java 1.5 hat man die Möglichkeit, für jeden Thread einen UncaughtExceptionHandler zu installieren. Darüber hinaus gibt es einen globalen Default-UncaughtExceptionHandler, der standardmäßig verwendet wird, falls kein Thread-spezifischer gesetzt wurde. Hier kann zentral festgelegt werden, was mit solchen Ausnahmen geschehen soll, beispielsweise die Darstellung eines Fehlerdialogs.


Wieso heißt es RuntimeException? Alle Exceptions fliegen doch zur Laufzeit.

Das ist richtig. Ursprünglich unterschied man zwischen Exceptions, die explizit vom Programmierer erzeugt und geworfen werden und solchen, die von der Java Laufzeitumgebung JRE, also der Virtuellen Maschine, implizit erzeugt werden. Wenn zum Beispiel versucht wird, eine Null-Referenz zu dereferenzieren oder über die Grenzen eines Arrays hinaus zuzugreifen, wirft die Java Laufzeitumgebung automatisch eine entsprechende Exception. Daher der Name RuntimeException.

Rein praktisch können RuntimeException natürlich auch explizit vom Programmierer erzeugt und geworfen werden, ganz genau wie geprüfte Ausnahmen.

Was ist ein Error?

Ein Error ist auch eine Art Exception, die eine schwere Ausnahmesituation darstellt. Sie kann in der Regel nicht behoben werden (z.B. OutOfMemoryError). Ähnlich wie RuntimeExceptions müssen Errors weder gefangen noch mit der throws-Klausel deklariert werden.


Was ist Throwable?

Throwable ist die Oberklasse für alle Exceptions, RuntimeExceptions und Errors - also alles, was geworfen werden kann.


Fragen

Das Thema wurde nicht ausreichend behandelt? Du hast Fragen dazu und brauchst weitere Informationen? Dann hole dir deine Antworten jetzt im Byte-Welt-Forum


Web-Links


--tfa 13:08, 30. Juli 2010 (CET)