Java Exceptions meistern: Unerwartete Fehler elegant behandeln
Was ist eine Exception?
Eine Exception (oder ein außergewöhnliches Ereignis) ist ein Problem, das während der Ausführung eines Programms auftritt. Wenn eine Exception auftritt, wird der normale Ablauf des Programms unterbrochen und das Programm/die Applikation beendet sich abnormal, was nicht empfohlen wird. Daher müssen diese Exceptions behandelt werden.
Eine Exception kann aus vielen verschiedenen Gründen auftreten. Folgende Szenarien führen zu Exceptions:
- Ein Benutzer hat ungültige Daten eingegeben.
- Eine Datei, die geöffnet werden muss, kann nicht gefunden werden.
- Eine Netzwerkverbindung wurde mitten in der Kommunikation verloren oder die JVM hat keinen Speicher mehr.
Einige dieser Exceptions werden durch Benutzerfehler verursacht, andere durch Programmierfehler und wieder andere durch physische Ressourcen, die auf irgendeine Weise versagt haben.
Basierend darauf gibt es drei Kategorien von Exceptions. Sie müssen sie verstehen, um zu wissen, wie Exception Handling in Java funktioniert.
Checked Exceptions (Überprüfte Ausnahmen)
Eine checked Exception ist eine Exception, die vom Compiler zur Kompilierzeit überprüft (benachrichtigt) wird. Diese werden auch als Compile-Time Exceptions bezeichnet. Diese Exceptions können nicht einfach ignoriert werden; der Programmierer sollte sich um diese Exceptions kümmern (sie behandeln).
Beispielsweise: Wenn Sie die FileReader-Klasse in Ihrem Programm verwenden, um Daten aus einer Datei zu lesen, und die in ihrem Konstruktor angegebene Datei nicht existiert, tritt eine FileNotFoundException auf, und der Compiler fordert den Programmierer auf, die Exception zu behandeln.
Beispiel
import java.io.File;
import java.io.FileReader;
public class FilenotFound_Demo {
public static void main(String args[]) {
File file = new File("E://file.txt");
FileReader fr = new FileReader(file);
}
}
Wenn Sie versuchen, das obige Programm zu kompilieren, erhalten Sie folgende Exceptions:
C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
FileReader fr = new FileReader(file);
^
1 error
Hinweis – Da die Methoden read() und close() der FileReader-Klasse IOException werfen, können Sie beobachten, dass der Compiler auffordert, IOException zusammen mit FileNotFoundException zu behandeln.
Unchecked Exceptions (Nicht überprüfte Ausnahmen)
Eine unchecked Exception ist eine Exception, die zur Ausführungszeit auftritt. Diese werden auch als Runtime Exceptions bezeichnet. Dazu gehören Programmierfehler wie Logikfehler oder unsachgemäße Verwendung einer API. Runtime-Exceptions werden zur Kompilierzeit ignoriert.
Beispielsweise: Wenn Sie in Ihrem Programm ein Array der Größe 5 deklariert haben und versuchen, das 6. Element des Arrays aufzurufen, tritt eine ArrayIndexOutOfBoundsException auf.
Beispiel
public class Unchecked_Demo {
public static void main(String args[]) {
int num[] = {1, 2, 3, 4};
System.out.println(num[5]);
}
}
Wenn Sie das obige Programm kompilieren und ausführen, erhalten Sie folgende Exception:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
Errors (Fehler)
Dies sind keine Exceptions, sondern Probleme, die außerhalb der Kontrolle des Benutzers oder Programmierers auftreten. Fehler werden in Ihrem Code normalerweise ignoriert, da Sie selten etwas gegen einen Fehler unternehmen können. Wenn beispielsweise ein Stack Overflow auftritt, entsteht ein Fehler. Sie werden auch zur Kompilierzeit ignoriert.
Exception-Hierarchie
Alle Exception-Klassen sind Subtypen der Klasse java.lang.Exception. Die Exception-Klasse ist eine Unterklasse der Throwable-Klasse. Außer der Exception-Klasse gibt es eine weitere Unterklasse namens Error, die von der Throwable-Klasse abgeleitet ist.
Errors sind abnormale Zustände, die bei schweren Fehlern auftreten; diese werden nicht von Java-Programmen behandelt. Errors werden generiert, um Fehler anzuzeigen, die von der Laufzeitumgebung generiert werden. Beispiel: JVM hat keinen Speicher mehr. Normalerweise können Programme sich nicht von Errors erholen.
Die Exception-Klasse hat zwei Hauptunterklassen: IOException-Klasse und RuntimeException-Klasse.
Wichtige Methoden der Throwable-Klasse
Folgende wichtige Methoden sind in der Throwable-Klasse verfügbar:
| Nr. | Methode & Beschreibung |
|---|---|
| 1 | public String getMessage() – Gibt eine detaillierte Meldung über die aufgetretene Exception zurück. Diese Meldung wird im Throwable-Konstruktor initialisiert. |
| 2 | public Throwable getCause() – Gibt die Ursache der Exception als Throwable-Objekt zurück. |
| 3 | public String toString() – Gibt den Namen der Klasse zusammen mit dem Ergebnis von getMessage() zurück. |
| 4 | public void printStackTrace() – Gibt das Ergebnis von toString() zusammen mit dem Stack Trace an System.err (den Fehlerausgabestream) aus. |
| 5 | public StackTraceElement[] getStackTrace() – Gibt ein Array zurück, das jedes Element auf dem Stack Trace enthält. Das Element an Index 0 repräsentiert den oberen Rand des Call Stacks, und das letzte Element im Array repräsentiert die Methode am unteren Rand des Call Stacks. |
| 6 | public Throwable fillInStackTrace() – Füllt den Stack Trace dieses Throwable-Objekts mit dem aktuellen Stack Trace, fügt ihn zu allen vorherigen Informationen im Stack Trace hinzu. |
Exceptions abfangen (Catching Exceptions)
Eine Methode fängt eine Exception mithilfe einer Kombination der Schlüsselwörter try und catch ab. Ein try/catch-Block wird um den Code platziert, der eine Exception erzeugen könnte. Code innerhalb eines try/catch-Blocks wird als geschützter Code bezeichnet. Die Syntax für die Verwendung von try/catch sieht wie folgt aus:
Syntax
try {
// Geschützter Code
} catch (ExceptionName e1) {
// Catch-Block
}
Der Code, der anfällig für Exceptions ist, wird in den try-Block platziert. Wenn eine Exception auftritt, wird diese von dem damit verbundenen catch-Block behandelt. Jeder try-Block sollte sofort entweder von einem catch-Block oder einem finally-Block gefolgt werden.
Eine catch-Anweisung beinhaltet die Deklaration des Exception-Typs, den Sie abfangen möchten. Wenn eine Exception im geschützten Code auftritt, wird der catch-Block (oder die Blöcke), der/die auf den try-Block folgt, geprüft. Wenn der Typ der aufgetretenen Exception in einem catch-Block aufgeführt ist, wird die Exception an den catch-Block übergeben, ähnlich wie ein Argument an einen Methodenparameter übergeben wird.
Beispiel
Das folgende Array ist mit 2 Elementen deklariert. Dann versucht der Code, auf das 3. Element des Arrays zuzugreifen, was eine Exception auslöst.
// Dateiname: ExcepTest.java
import java.io.*;
public class ExcepTest {
public static void main(String args[]) {
try {
int a[] = new int[2];
System.out.println("Access element three :" + a[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Exception thrown :" + e);
}
System.out.println("Out of the block");
}
}
Ausgabe:
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block
Mehrere Catch-Blöcke
Ein try-Block kann von mehreren catch-Blöcken gefolgt werden. Die Syntax für mehrere catch-Blöcke sieht wie folgt aus:
Syntax
try {
// Geschützter Code
} catch (ExceptionType1 e1) {
// Catch-Block
} catch (ExceptionType2 e2) {
// Catch-Block
} catch (ExceptionType3 e3) {
// Catch-Block
}
Die obigen Aussagen demonstrieren drei catch-Blöcke, aber Sie können beliebig viele nach einem einzelnen try haben. Wenn eine Exception im geschützten Code auftritt, wird die Exception an den ersten catch-Block in der Liste geworfen. Wenn der Datentyp der geworfenen Exception mit ExceptionType1 übereinstimmt, wird er dort abgefangen. Wenn nicht, geht die Exception an den zweiten catch-Block weiter. Dies setzt sich fort, bis die Exception entweder abgefangen wird oder durch alle catches durchfällt, in welchem Fall die aktuelle Methode die Ausführung stoppt und die Exception an die vorherige Methode auf dem Call Stack weitergeworfen wird.
Beispiel
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch (IOException i) {
i.printStackTrace();
return -1;
} catch (FileNotFoundException f) // Nicht gültig! {
f.printStackTrace();
return -1;
}
Mehrere Exception-Typen in einem Catch-Block abfangen (ab Java 7)
Seit Java 7 können Sie mehr als eine Exception mit einem einzelnen catch-Block behandeln, was den Code vereinfacht. So geht es:
catch (IOException | FileNotFoundException ex) {
logger.log(ex);
throw ex;
}
Die Schlüsselwörter throws / throw
Wenn eine Methode eine checked Exception nicht behandelt, muss die Methode sie mit dem Schlüsselwort throws deklarieren. Das throws-Schlüsselwort erscheint am Ende der Methodensignatur.
Sie können eine Exception werfen, entweder eine neu instanziierte oder eine, die Sie gerade abgefangen haben, indem Sie das Schlüsselwort throw verwenden.
Versuchen Sie, den Unterschied zwischen throws und throw zu verstehen: throws wird verwendet, um die Behandlung einer checked Exception hinauszuzögern, und throw wird verwendet, um eine Exception explizit auszulösen.
Beispiel: throws
import java.io.*;
public class ClassName {
public void deposit(double amount) throws RemoteException {
// Methodenimplementierung
throw new RemoteException();
}
// Rest der Klassendefinition
}
Eine Methode kann deklarieren, dass sie mehr als eine Exception wirft. In diesem Fall werden die Exceptions in einer durch Kommas getrennten Liste deklariert. Beispielsweise deklariert die folgende Methode, dass sie eine RemoteException und eine InsufficientFundsException wirft:
import java.io.*;
public class ClassName {
public void withdraw(double amount) throws RemoteException, InsufficientFundsException {
// Methodenimplementierung
}
// Rest der Klassendefinition
}
Der finally-Block
Der finally-Block folgt auf einen try-Block oder einen catch-Block. Ein finally-Block wird immer ausgeführt, unabhängig davon, ob eine Exception aufgetreten ist oder nicht.
Die Verwendung eines finally-Blocks ermöglicht es Ihnen, Bereinigungsanweisungen auszuführen, die Sie ausführen möchten, egal was im geschützten Code passiert.
Ein finally-Block erscheint am Ende der catch-Blöcke und hat folgende Syntax:
Syntax
try {
// Geschützter Code
} catch (ExceptionType1 e1) {
// Catch-Block
} catch (ExceptionType2 e2) {
// Catch-Block
} catch (ExceptionType3 e3) {
// Catch-Block
} finally {
// Der finally-Block wird immer ausgeführt.
}
Beispiel
public class ExcepTest {
public static void main(String args[]) {
int a[] = new int[2];
try {
System.out.println("Access element three :" + a[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Exception thrown :" + e);
} finally {
a[0] = 6;
System.out.println("First element value: " + a[0]);
System.out.println("The finally statement is executed");
}
}
}
Ausgabe:
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed
Beachten Sie Folgendes:
- Eine catch-Klausel kann nicht ohne eine try-Anweisung existieren.
- Es ist nicht zwingend erforderlich, finally-Klauseln zu haben, wann immer ein try/catch-Block vorhanden ist.
- Der try-Block kann nicht ohne entweder eine catch-Klausel oder eine finally-Klausel vorhanden sein.
- Kein Code kann zwischen den try-, catch-, finally-Blöcken stehen.
try-with-resources (automatische Ressourcenverwaltung)
Generell müssen wir, wenn wir Ressourcen wie Streams, Verbindungen usw. verwenden, diese explizit mit einem finally-Block schließen. Im folgenden Programm lesen wir Daten aus einer Datei mit FileReader und schließen sie mit einem finally-Block.
Beispiel: Klassischer Ansatz mit finally
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ReadData_Demo {
public static void main(String args[]) {
FileReader fr = null;
try {
File file = new File("file.txt");
fr = new FileReader(file);
char[] a = new char[50];
fr.read(a); // Inhalt in Array lesen
for (char c : a)
System.out.print(c); // Zeichen einzeln ausgeben
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
try-with-resources, auch als automatic resource management bezeichnet, ist ein neuer Exception-Handling-Mechanismus, der in Java 7 eingeführt wurde und die im try-catch-Block verwendeten Ressourcen automatisch schließt.
Um diese Anweisung zu verwenden, müssen Sie einfach die erforderlichen Ressourcen in Klammern deklarieren, und die erstellte Ressource wird am Ende des Blocks automatisch geschlossen. Folgende Syntax gilt für try-with-resources:
Syntax
try (FileReader fr = new FileReader("file path")) {
// Ressource verwenden
} catch () {
// catch-Body
}
Beispiel: try-with-resources
import java.io.FileReader;
import java.io.IOException;
public class Try_withDemo {
public static void main(String args[]) {
try (FileReader fr = new FileReader("E://file.txt")) {
char[] a = new char[50];
fr.read(a); // Inhalt in Array lesen
for (char c : a)
System.out.print(c); // Zeichen einzeln ausgeben
} catch (IOException e) {
e.printStackTrace();
}
}
}
Wichtige Punkte bei der Arbeit mit try-with-resources:
- Um eine Klasse mit try-with-resources zu verwenden, muss sie das AutoCloseable-Interface implementieren, und ihre
close()-Methode wird zur Laufzeit automatisch aufgerufen. - Sie können mehr als eine Klasse in der try-with-resources-Anweisung deklarieren.
- Wenn Sie mehrere Klassen im try-Block von try-with-resources deklarieren, werden diese in umgekehrter Reihenfolge geschlossen.
- Abgesehen von der Deklaration der Ressourcen in den Klammern ist alles gleich wie bei einem normalen try/catch-Block.
- Die im try deklarierte Ressource wird unmittelbar vor dem Start des try-Blocks instanziiert.
- Die im try-Block deklarierte Ressource ist implizit als
finaldeklariert.
Benutzerdefinierte Exceptions (User-defined Exceptions)
Sie können in Java Ihre eigenen Exceptions erstellen. Beachten Sie folgende Punkte beim Schreiben eigener Exception-Klassen:
- Alle Exceptions müssen ein Kind von
Throwablesein. - Wenn Sie eine checked Exception schreiben möchten, die automatisch durch die Handle-or-Declare-Regel erzwungen wird, müssen Sie die
Exception-Klasse erweitern. - Wenn Sie eine Runtime-Exception schreiben möchten, müssen Sie die
RuntimeException-Klasse erweitern.
Wir können unsere eigene Exception-Klasse wie folgt definieren:
class MyException extends Exception {
}
Sie müssen lediglich die vordefinierte Exception-Klasse erweitern, um Ihre eigene Exception zu erstellen. Diese gelten als checked Exceptions. Die folgende InsufficientFundsException-Klasse ist eine benutzerdefinierte Exception, die die Exception-Klasse erweitert und damit zu einer checked Exception wird. Eine Exception-Klasse ist wie jede andere Klasse und enthält nützliche Felder und Methoden.
Beispiel: InsufficientFundsException
// Dateiname: InsufficientFundsException.java
import java.io.*;
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
Um die Verwendung unserer benutzerdefinierten Exception zu demonstrieren, enthält die folgende CheckingAccount-Klasse eine withdraw()-Methode, die eine InsufficientFundsException wirft.
// Dateiname: CheckingAccount.java
import java.io.*;
public class CheckingAccount {
private double balance;
private int number;
public CheckingAccount(int number) {
this.number = number;
}
public void deposit(double amount) {
balance += amount;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= balance) {
balance -= amount;
} else {
double needs = amount - balance;
throw new InsufficientFundsException(needs);
}
}
public double getBalance() {
return balance;
}
public int getNumber() {
return number;
}
}
Das folgende BankDemo-Programm demonstriert das Aufrufen der deposit()- und withdraw()-Methoden von CheckingAccount.
// Dateiname: BankDemo.java
public class BankDemo {
public static void main(String[] args) {
CheckingAccount c = new CheckingAccount(101);
System.out.println("Depositing $500...");
c.deposit(500.00);
try {
System.out.println("\nWithdrawing $100...");
c.withdraw(100.00);
System.out.println("\nWithdrawing $600...");
c.withdraw(600.00);
} catch (InsufficientFundsException e) {
System.out.println("Sorry, but you are short $" + e.getAmount());
e.printStackTrace();
}
}
}
Kompilieren Sie alle drei Dateien und führen Sie BankDemo aus. Dies erzeugt folgendes Ergebnis:
Ausgabe
Depositing $500...
Withdrawing $100...
Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
at CheckingAccount.withdraw(CheckingAccount.java:25)
at BankDemo.main(BankDemo.java:13)
Häufige Exceptions
In Java ist es möglich, zwei Kategorien von Exceptions und Errors zu definieren:
- JVM Exceptions – Dies sind Exceptions/Errors, die ausschließlich oder logischerweise von der JVM geworfen werden. Beispiele:
NullPointerException,ArrayIndexOutOfBoundsException,ClassCastException. - Programmatic Exceptions – Diese Exceptions werden explizit von der Applikation oder den API-Programmierern geworfen. Beispiele:
IllegalArgumentException,IllegalStateException.
Java
- Java Operatoren meistern: Typen, Syntax und praktische Beispiele
- Java Kommentare: Typen, Verwendung und Best Practices
- Java Enhanced For-Schleife meistern: Syntax, Beispiele und Best Practices
- Java Strings meistern: Erstellung, Methoden und Best Practices
- Java Exception Handling meistern: Try, Catch, Finally, Throw & Throws erklärt
- Java throw vs. throws: Exception Handling mit praktischen Beispielen meistern
- Multi-Exception Handling in Java meistern
- Java Try-With-Resources meistern: Automatisches Ressourcenmanagement erklärt
- Java Annotations erklärt: Typen, Platzierung und praktische Beispiele
- Java Assertions: Praktischer Leitfaden zur Fehlererkennung und Code-Reliabilität