Was ist eine Exception?

tfa

Top Contributor
Was ist eine Exception?

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.

Lesson: Exceptions (The Java™ Tutorials > Essential Classes)

Was ist der Unterschied zwischen checked und unchecked Exceptions?

Unchecked Exceptions sind von der Klasse
Code:
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
Code:
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.

Java:
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.

Java:
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.

[1] 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 Runtime-Exception, 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 Exceptionbehandlung
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.

[JavaSpecialists 033] - Making Exceptions Unchecked
Java's checked exceptions were a mistake (and here's what I would like to do about it)
Bruce Eckel's MindView, Inc: Does Java need Checked Exceptions?


Was tun mit all den unbehandelten Exceptions?

Runtime-Exceptions 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.
 

Oben