Ausnahmen (Exceptions)
Wenn die Programmausführung mit einem Fehler endet, wird eine Ausnahme ausgelöst. Beispielsweise könnte ein Programm eine Methode mit einer null-Referenz aufrufen und eine NullPointerException
auslösen, oder das Programm könnte versuchen, auf ein Element außerhalb eines Arrays zuzugreifen, was zu einer IndexOutOfBoundsException
führt, und so weiter.
Für einige Ausnahmen müssen wir immer vorbereitet sein, wie z.B. bei Fehlern beim Lesen einer Datei oder bei Problemen mit einer Netzwerkverbindung. Für Laufzeitausnahmen, wie die NullPointerException, müssen wir uns nicht im Voraus vorbereiten. Java wird Ihnen immer mitteilen, wenn Ihr Code eine Anweisung oder einen Ausdruck enthält, der eine Ausnahme auslösen könnte, für die Sie vorbereitet sein müssen.
Umgang mit Ausnahmen
Wir verwenden die try {} catch (Exception e) {}
-Struktur, um Ausnahmen zu behandeln. Das Schlüsselwort try
beginnt einen Block, der den Code enthält, der möglicherweise eine Ausnahme auslösen könnte. Der Block, der mit dem Schlüsselwort catch
beginnt, definiert, was passiert, wenn eine Ausnahme im try
-Block ausgelöst wird. Das Schlüsselwort catch
wird gefolgt von der Art der Ausnahme, die in diesem Block behandelt wird, beispielsweise "alle Ausnahmen" catch (Exception e)
.
try {
// code which possibly throws an exception
} catch (Exception e) {
// code block executed if an exception is thrown
}
Wir verwenden das Schlüsselwort catch
, weil das Auslösen einer Ausnahme als throw
ing einer Ausnahme bezeichnet wird.
Wie oben erwähnt, müssen wir uns nicht im Voraus auf Laufzeitausnahmen wie die NullPointerException vorbereiten. Solche Ausnahmen müssen nicht behandelt werden, sodass die Programmausführung gestoppt wird, wenn ein Fehler die Ausnahme auslöst. Als Nächstes betrachten wir eine solche Situation: das Parsen von Zeichenketten in Ganzzahlen.
Wir haben die parseInt-Methode der Klasse Integer
bereits verwendet. Die Methode wirft (throws) eine NumberFormatException
, wenn die ihr übergebene Zeichenkette nicht in eine Ganzzahl umgewandelt werden kann.
Scanner reader = new Scanner(System.in);
System.out.print("Give a number: ");
int readNumber = Integer.parseInt(reader.nextLine());
Give a number: dinosaur
Exception in thread "..." java.lang.NumberFormatException: For input string: "dinosaur"
Das obige Programm wirft einen Fehler (throws an exception), wenn die Benutzereingabe keine gültige Zahl ist. Die Ausnahme führt dazu, dass die Programmausführung stoppt.
Lassen Sie uns die Ausnahme behandeln. Wir umschließen den Aufruf, der möglicherweise eine Ausnahme auslöst, in einen try
-Block, und der Code, der ausgeführt wird, wenn die Ausnahme ausgelöst wird, kommt in einen catch
-Block.
Scanner reader = new Scanner(System.in);
System.out.print("Give a number: ");
int readNumber = -1;
try {
readNumber = Integer.parseInt(reader.nextLine());
} catch (Exception e) {
System.out.println("User input was not a number.");
}
Give a number: 5
Give a number: no! User input was not a number.
Der Code im catch
-Block wird sofort ausgeführt, wenn der Code im try
-Block eine Ausnahme auslöst. Wir können dies demonstrieren, indem wir eine Ausgabeanweisung unterhalb der Zeile, die die Methode Integer.parseInt
im try
-Block aufruft, hinzufügen.
Scanner reader = new Scanner(System.in);
System.out.print("Give a number: ");
int readNumber = -1;
try {
readNumber = Integer.parseInt(reader.nextLine());
System.out.println("Good job!");
} catch (Exception e) {
System.out.println("User input was not a number.");
}
Give a number: 5 Good job!
Give a number: no! User input was not a number.
Die Benutzereingabe, in diesem Fall die Zeichenkette no!
, wird der Methode Integer.parseInt
als Parameter übergeben. Die Methode wirft einen Fehler, wenn die Zeichenkette nicht in eine Ganzzahl umgewandelt werden kann. Beachten Sie, dass der Code im catch
-Block nur ausgeführt wird, wenn eine Ausnahme ausgelöst wird.
Lassen Sie uns unseren Ganzzahlparser etwas nützlicher machen. Wir werden ihn in eine Methode umwandeln, die den Benutzer auffordert, eine Zahl einzugeben, bis eine gültige Zahl eingegeben wird. Die Ausführung endet nur, wenn der Benutzer eine gültige Zahl eingibt.
public int readNumber(Scanner reader) {
while (true) {
System.out.print("Give a number: ");
try {
int readNumber = Integer.parseInt(reader.nextLine());
return readNumber;
} catch (Exception e) {
System.out.println("User input was not a number.");
}
}
}
Give a number: no! User input was not a number. Give a number: Matt has moss in his head User input was not a number. Give a number: 43
Ausnahmen und Ressourcen
Für das Lesen von Betriebssystemressourcen wie Dateien gibt es eine separate Ausnahmebehandlung. Bei der sogenannten try-with-resources-Ausnahmebehandlung wird die zu öffnende Ressource als optionaler Teil der try-Block-Deklaration in Klammern hinzugefügt.
Der folgende Code liest alle Zeilen aus "file.txt" und fügt sie einer ArrayList hinzu. Das Lesen einer Datei könnte eine Ausnahme auslösen, daher ist ein try-catch-Block erforderlich.
ArrayList<String> lines = new ArrayList<>();
// create a Scanner object for reading files
try (Scanner reader = new Scanner(new File("file.txt"))) {
// read all lines from a file
while (reader.hasNextLine()) {
lines.add(reader.nextLine());
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
// do something with the lines
Der try-with-resources-Ansatz ist nützlich für den Umgang mit Ressourcen, da das Programm die verwendeten Ressourcen automatisch schließt. Jetzt können Referenzen auf Dateien "verschwinden", weil wir sie nicht mehr benötigen. Wenn die Ressourcen nicht geschlossen werden, betrachtet das Betriebssystem sie als in Gebrauch, bis das Programm geschlossen wird.
Verantwortung weitergeben
Methoden und Konstruktoren können Ausnahmen werfen. Es gibt grob gesagt zwei Kategorien von Ausnahmen. Es gibt Ausnahmen, die wir behandeln müssen, und Ausnahmen, die wir nicht behandeln müssen. Wir können Ausnahmen behandeln, indem wir den Code in einen try-catch
-Block einfügen oder indem wir sie aus der Methode herauswerfen (throw them out of the method).
Der folgende Code liest die Zeilen der Datei, die ihm als Parameter übergeben wurde, Zeile für Zeile. Das Lesen einer Datei kann eine Ausnahme auslösen — zum Beispiel könnte die Datei nicht existieren oder das Programm hat keine Leserechte für die Datei. Diese Art von Ausnahme muss behandelt werden. Wir behandeln die Ausnahme, indem wir den Code in einen try-catch
-Block einfügen. In diesem Beispiel ist uns die Ausnahme nicht wirklich wichtig, aber wir geben dem Benutzer eine Nachricht darüber aus.
public List<String> readLines(String fileName){
List<String> lines = new ArrayList<>();
try {
Files.lines(Paths.get("file.txt")).forEach(line -> lines.add(line));
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
return lines;
}
Eine Programmiererin oder ein Programmierer kann die Ausnahme auch unbehandelt lassen und die Verantwortung für die Behandlung auf die Methode, die die Methode aufruft, übertragen. Wir können die Verantwortung für die Behandlung einer Ausnahme weitergeben, indem wir die Ausnahme aus einer Methode werfen und einen Hinweis darauf in die Methodendeklaration aufnehmen. Der Hinweis auf das Weiterwerfen einer Ausnahme throws *ExceptionType*
wird vor die öffnende Klammer einer Methode hinzugefügt.
public List<String> readLines(String fileName) throws Exception {
ArrayList<String> lines = new ArrayList<>();
Files.lines(Paths.get(fileName)).forEach(line -> lines.add(line));
return lines;
}
Jetzt muss die Methode, die die readLines
-Methode aufruft, entweder die Ausnahme in einem try-catch
-Block behandeln oder die Verantwortung weitergeben. Manchmal wird die Verantwortung für die Behandlung von Ausnahmen bis zum Ende vermieden, und selbst die main
-Methode kann eine Ausnahme an den Aufrufer werfen:
public class MainProgram {
public static void main(String[] args) throws Exception {
// ...
}
}
Jetzt wird die Ausnahme an den Programmausführer oder die Java Virtual Machine weitergegeben. Diese stoppt die Programmausführung, wenn ein Fehler, der eine Ausnahme auslöst, auftritt.
Ausnahmen werfen
Der throw
-Befehl wirft eine Ausnahme. Beispielsweise kann eine NumberFormatException
mit dem Befehl throw new NumberFormatException()
geworfen werden. Der folgende Code wirft immer eine Ausnahme.
public class Program {
public static void main(String[] args) throws Exception {
throw new NumberFormatException(); // Program throws an exception
}
}
Eine Ausnahme, auf die die Benutzerin oder der Benutzer nicht vorbereitet sein muss, ist IllegalArgumentException
. Die IllegalArgumentException
informiert darüber, dass die an eine Methode oder einen Konstruktor übergebenen Parameterwerte falsch sind. Sie kann verwendet werden, wenn wir bestimmte Parameterwerte sicherstellen wollen.
Lassen Sie uns die Klasse Grade
erstellen. Sie erhält eine ganze Zahl, die eine Note darstellt, als Konstruktorparameter.
public class Grade {
private int grade;
public Grade(int grade) {
this.grade = grade;
}
public int getGrade() {
return this.grade;
}
}
Wir möchten, dass die Note bestimmte Kriterien erfüllt. Die Note muss eine ganze Zahl zwischen 0 und 5 sein. Wenn sie etwas anderes ist, wollen wir eine Ausnahme werfen. Fügen wir eine Bedingungsanweisung in den Konstruktor ein, die überprüft, ob die Note die Kriterien erfüllt. Wenn dies nicht der Fall ist, werfen wir die IllegalArgumentException
mit throw new IllegalArgumentException("Grade must be between 0 and 5.");
.
public class Grade {
private int grade;
public Grade(int grade) {
if (grade < 0 || grade > 5) {
throw new IllegalArgumentException("Grade must be between 0 and 5.");
}
this.grade = grade;
}
public int getGrade() {
return this.grade;
}
}
Grade grade = new Grade(3);
System.out.println(grade.getGrade());
Grade illegalGrade = new Grade(22);
// exception happens, execution will not continue from here
3 Exception in thread "..." java.lang.IllegalArgumentException: Grade must be between 0 and 5.
Wenn eine Ausnahme eine Laufzeitausnahme ist, z.B. IllegalArgumentException, müssen wir nicht in der Methodendeklaration darauf hinweisen, dass sie geworfen wird.
Ausnahmen und Schnittstellen
Eine Schnittstelle kann Methoden haben, die eine Ausnahme werfen. Beispielsweise können die Klassen, die das folgende FileServer
-Interface implementieren, möglicherweise eine Ausnahme aus den Methoden load
oder save
werfen.
public interface FileServer {
String load(String fileName) throws Exception;
void save(String fileName, String textToSave) throws Exception;
}
Wenn eine Schnittstelle einer Methode ein throws Exception
-Attribut hinzufügt, sodass diese Methoden möglicherweise eine Ausnahme werfen, muss die Klasse, die diese Schnittstelle implementiert, auch dieses Attribut haben. Die Klasse muss jedoch keinen Fehler werfen, wie wir unten sehen können.
public class TextServer implements FileServer {
private Map<String, String> data;
public TextServer() {
this.data = new HashMap<>();
}
@Override
public String load(String fileName) throws Exception {
return this.data.get(fileName);
}
@Override
public void save(String fileName, String textToSave) throws Exception {
this.data.put(fileName, textToSave);
}
}
Details der Ausnahme
Ein catch
-Block definiert, welche Ausnahme mit catch (Exception e)
vorbereitet wird. Die Details der Ausnahme werden in der Variable e
gespeichert.
try {
// program code which might throw an exception
} catch (Exception e) {
// details of the exception are stored in the variable e
}
Die Klasse Exception
hat einige nützliche Methoden. Zum Beispiel druckt printStackTrace()
den Stack Trace aus, der zeigt, wie wir zu einer Ausnahme gekommen sind. Unten ist ein Stack Trace abgebildet, der von der printStackTrace()
-Methode ausgegeben wurde.
Exception in thread "main" java.lang.NullPointerException at package.Class.print(Class.java:43) at package.Class.main(Class.java:29)
Wir lesen einen Stack Trace von unten nach oben. Ganz unten befindet sich der erste Aufruf, also die Ausführung des Programms hat mit der main()
-Methode der Class
-Klasse begonnen. Zeile 29 der main
-Methode der Class
-Klasse ruft die Methode print()
auf. Zeile 43 der print
-Methode hat eine NullPointerException
ausgelöst. Die Details einer Ausnahme sind sehr nützlich, wenn versucht wird, den Ort eines Fehlers zu lokalisieren.
Loading...