8.3 Ungeprüfte Ausnahmen (RuntimeException)
Einige Arten von Ausnahmen können potenziell an vielen Programmstellen auftreten, etwa eine ganzzahlige Division durch null[ 181 ](Fließkommadivisionen durch 0,0 ergeben entweder ± unendlich oder NaN. ) oder ungültige Indexwerte beim Zugriff auf Array-Elemente. Treten solche Ausnahmen beim Programmlauf auf, liegt ihnen in der Regel ein Denkfehler des Programmierers zugrunde, und das Programm sollte normalerweise nicht versuchen, die ausgelöste Ausnahme aufzufangen und zu behandeln. Daher gibt es in der Java-API mit der Klasse RuntimeException eine Unterklasse von Exception, die Programmierfehler aufzeigt, die behoben werden müssen. (Der Name »RuntimeException« ist jedoch seltsam gewählt, da alle Ausnahmen immer zur Runtime, also zur Laufzeit, erzeugt, ausgelöst und behandelt werden. Doch drückt er aus, dass der Compiler sich für diese Ausnahmen nicht interessiert, sondern erst die JVM zur Laufzeit.)
[»] Hinweis
Es funktioniert gut, eine RuntimeException als Selbst-schuld-Fehler zu sehen. Durch sachgemäße Prüfung, z. B. der Wertebereiche, würden viele RuntimeExceptions nicht entstehen.
8.3.1 Eine NumberFormatException fliegt
Über die Methode Integer.parseInt(…) haben wir an verschiedenen Stellen schon gesprochen. Sie konvertiert eine Zahl, die als Zeichenkette gegeben ist, in eine Dezimalzahl:
int vatRate = Integer.parseInt( "19" );
In dem Beispiel ist eine Konvertierung möglich, und die Methode führt die Umwandlung ohne Ausnahme aus. Anders sieht das aus, wenn der String keine Zahl repräsentiert:
package com.tutego.insel.exception; /* 1 */
public class MissNumberFormatException { /* 2 */
public static int getVatRate() { /* 3 */
return Integer.parseInt( "19%" ); /* 4 */
} /* 5 */
public static void main( String[] args ) { /* 6 */
System.out.println( getVatRate() ); /* 7 */
} /* 8 */
} /* 9 */
Die Ausführung des Programms bricht mit einer Ausnahme ab, und die virtuelle Maschine gibt uns automatisch eine Meldung aus:
Exception in thread "main" java.lang.NumberFormatException: For input string: "19%"
at java.base/java.lang.NumberFormatException.forInputString(
NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at c.t.i.e.MissNumberFormatException.getVatRate(MissNumberFormatException.java:4)
at e.t.i.e.MissNumberFormatException.main(MissNumberFormatException.java:7)
In der ersten Zeile können wir ablesen, dass eine java.lang.NumberFormatException ausgelöst wurde. In der letzten Zeile steht, welche Stelle in unserem Programm zu der Ausnahme führte. (Fehlerausgaben wie diese haben wir schon im Abschnitt »Auf null geht nix, nur die NullPointerException« in Abschnitt 3.7.1, »null-Referenz und die Frage der Philosophie«, beobachtet.)
Eine NumberFormatException auffangen
Dass ein Programm einfach so abbricht und die JVM endet, ist üblicherweise keine Lösung. Ausnahmen sollten aufgefangen und gemeldet werden. Um Ausnahmen aufzufangen, ist es erst einmal wichtig, zu wissen, was genau für eine Ausnahme ausgelöst wird. In unserem Fall ist das einfach abzulesen, denn die Ausnahme ist ja schon aufgetaucht und klar einem Grund zuzuordnen. Die Java-Dokumentation nennt diese Ausnahme auch, und weil ohne die aufgefangene Ausnahme das Programm abbricht, soll nun die NumberFormatException aufgefangen werden. Dabei kommt wieder die try-catch-Konstruktion zum Einsatz:
String stringToConvert = "19%";
double vat = 19;
try {
vat = Integer.parseInt( stringToConvert );
}
catch ( NumberFormatException e ) {
System.err.printf( "'%s' kann man nicht in eine Zahl konvertieren!%n",
stringToConvert );
}
System.out.printf( "Weiter geht's mit MwSt=%g%n", vat );
'19%' kann man nicht in eine Zahl konvertieren!
Weiter geht's mit MwSt=19,0000
Integer.parseInt("19%") führt, da der String keine Zahl ist, zu einer NumberFormatException, die wir behandeln, und danach geht es mit der Konsolenausgabe weiter.
8.3.2 Bekannte RuntimeException-Klassen
Die Java-API bietet insgesamt eine große Anzahl von RuntimeException-Klassen, und es werden immer mehr. Tabelle 8.1 listet einige bekannte Ausnahmetypen auf und zeigt, welche Operationen die Ausnahmen auslösen. Wir greifen hier schon auf spezielle APIs vor, die erst später im Buch vorgestellt werden.
Unterklasse von RuntimeException | Was den Fehler auslöst |
---|---|
ganzzahlige Division durch 0 | |
Indexgrenzen wurden missachtet, etwa durch (new int[0])[1]. Eine ArrayIndexOutOfBoundsException ist neben der StringIndexOutOfBoundsException eine Unterklasse von IndexOutOfBoundsException. | |
Eine Typumwandlung ist zur Laufzeit nicht möglich. So löst String s = (String) new Object(); eine ClassCastException mit der Meldung »java.lang. Object cannot be cast to java.lang.String« aus. | |
Der Stapelspeicher ist leer. new java.util.Stack(). pop() provoziert den Fehler. | |
Eine häufig verwendete Ausnahme, mit der Methoden falsche Argumente melden. Integer.parseInt("tutego") löst eine NumberFormatException, eine Unterklasse von IllegalArgumentException, aus. | |
Ein Thread möchte warten, hat aber den Monitor nicht. Ein Beispiel: new String().wait(); | |
Meldet einen der häufigsten Programmierfehler, beispielsweise durch ((String) null).length(). | |
Operationen sind nicht gestattet, etwa durch java.util.Arrays.asList(args).add("jv"). |
8.3.3 Kann man abfangen, muss man aber nicht
Eine RuntimeException kann im Code abgefangen werden, muss es aber nicht. Da der Compiler nicht auf einem Abfangen besteht, heißen die aus RuntimeException hervorgegangenen Ausnahmen auch ungeprüfte Ausnahmen bzw. nichtgeprüfte Ausnahmen (engl. unchecked exceptions), und alle übrigen heißen geprüfte Ausnahmen (engl. checked exceptions). Auch muss eine RuntimeException nicht unbedingt bei throws in der Methodensignatur angegeben werden, obwohl einige Autoren das zur Dokumentation machen.
Praktisch ist, dass eine ungeprüfte Ausnahme entlang der Kette von Methodenaufrufen wie eine Blase (engl. bubble) nach oben steigen und irgendwann von einem Block abgefangen werden kann, der sich darum kümmert. Tritt eine RuntimeException zur Laufzeit auf und kommt nicht irgendwann in der Aufrufhierarchie ein try-catch, beendet die JVM den ausführenden Thread. Löst also eine in main(…) aufgerufene Aktion eine RuntimeException aus, ist das das Ende für dieses Hauptprogramm.
[+] Style
Eine RuntimeException wird nicht im Methodenkopf angegeben, sollte aber im Javadoc dokumentiert werden.