Finally
finally-Block
- Optional kann ein
finally
-Block angegeben werden - Dient Aufräumarbeiten
- Schließen von Datenbankverbindungen und Dateien
- Freigabe von Ressourcen, etc.
- Wird immer ausgeführt, egal wie
try
-Block verlassen wurde - durch normalen Ablauf
- durch explizites werfen einer Ausnahme
- durch fangen und behandeln einer Ausnahme
- durch eine Ausnahme, die nicht gefangen wird
- durch ein
return
-Statement
Es kommt sehr häufig vor, dass man bestimmte Aktionen vorbereitet (z. B. eine Datei öffnet), dann eine Aktion durchführt (in die Datei schreibt) und dann Aufräumarbeiten durchführen muss (die Datei schließen). Wenn jetzt während der Durchführung ein Fehler auftreten kann, wird man entsprechende try
-catch
-Block benutzen. Tritt ein Fehler auf, so muss man in den meisten Fällen trotzdem die Aufräumarbeiten durchführen. Würde man diese im try
-Block machen, würden sie im Fehlerfall nicht ausgeführt. Macht man sie im catch
-Block, werden sie nur im Fehlerfall ausgeführt. Es fehlt also die Möglichkeit, Aufräumarbeiten sowohl im Fehler- als auch im Nicht-Fehler-Fall durchzuführen.
Genau zu diesem Zweck dient der finally
-Block, der durch das Schlüsselwort finally
eingeleitet wird. Die Anweisungen in diesem Block werden auf jeden Fall ausgeführt, egal, ob ein Fehler auftritt oder nicht. Sogar wenn der try
- oder catch
-Block mit return
verlassen werden sollte, wird vorher noch der finally
-Block ausgeführt, bevor die Methode zurückkehrt.
finally
kann auch ohne catch
benutzt werden
Connection connection = getConnection();
try {
ResultSet rs = readData(connection);
while (rs.next()) {
// Daten bearbeiten
}
return;
}
finally {
releaseConnection(connection);
}
Beispiel: finally-Block
Connection connection = getConnection();
try {
ResultSet rs = readData(connection);
while (rs.next()) {
// Daten bearbeiten
}
return;
} catch (SQLException ex) {
// Datenbank Problem behandeln
} finally {
releaseConnection(connection);
}
In diesem Beispiel garantiert der finally
-Block, dass die Methode releaseConnection()
auf jeden Fall durchgeführt wird, auch wenn der try
-Block durch einen Fehler abrupt beendet werden sollte.
Ausnahmen im finally-Block
Connection connection = getConnection();
try {
ResultSet rs = readData(connection);
while (rs.next()) {
// Daten bearbeiten
}
} catch (SQLException ex) {
// Datenbank Problem behandeln
} finally {
try {
releaseConnection(connection);
}
catch (SQLException ex) {
// Hier kann man nichts mehr machen
}
}
Da der finally
-Block für Aufräumarbeiten gedacht ist, sollte man besonders sorgfältig auf den Fall achten, dass er selbst durch eine Exception beendet wird. Normalerweise kann man Ausnahmen, die beim Aufräumen entstehen nicht mehr adäquat behandeln. Deshalb findet man häufig die Konstruktion, dass in einem finally
-Block noch einmal ein try
-catch
-Block geschachtelt ist, der die Ausnahmen beim Aufräumen fängt. Der catch
-Block ist in diesem Fall häufig leer, was an anderer Stelle unerwünscht, im finally
-Block aber normal ist.
Auf jeden Fall sollte man verhindern, dass der finally
-Block durch eine Ausnahme oder ein return
abrupt beendet wird. Das folgende Beispiel zeigt warum.
Abruptes Beenden des finally-Blocks
public class Finally {
public static boolean f() {
try {
return true;
} finally {
return false;
}
}
public static void main(String[] args) {
System.out.println(f());
}
}
false
Ein abruptes Beenden des finally
-Blocks ist normalerweise nicht erwünscht und gilt als Programmierfehler. Im Beispiel kann man sehen, was passiert, wenn er trotzdem durch ein return
abrupt beendet wird: der vom finally
-Block zurückgegebene Wert wird letztendlich aus der Methode zurückgeliefert und nicht der vom try
-Block. Das return
im try
-Block wird zwar ausgeführt, hat aber keine Wirkung. Eventuelle Seiteneffekte in diesem würden aber auftreten.
try-with-resources Statement
- Im Kopf eines try/
catch
-Blockes können Ressourcen (z. B. Streams oder Datenbank-Verbindungen) angefordert werden - Diese werden automatisch beim Verlassen Blocks geschlossen, ohne dass man dies explizit im
finally
-Block machen müsste - Basis ist ein neues Interface
AutoCloseable
, das die Resource implementieren muss
try(FileInputStream fis = new FileInputStream("/tmp/file1");
FileOutputStream fos = new FileOutputStream("/tmp/file2")) {
int i = fis.read();
fos.write(i);
}
catch (IOException e) {
// Ausnahme behandeln
}
Eines der lästigen Probleme beim Umgang mit Dateien und Netzwerkverbindungen ist, dass man sie korrekt schließen muss und zwar auch dann, wenn ein Fehler aufgetreten ist. Damit man sie aber in einem finally
-Block schließen kann, muss man die Referenzen vor dem try
-Block deklarieren, da sie ansonsten im finally
-Block nicht sichtbar sind. Hinzu kommt noch, dass finally
innerhalb der VM für einen erheblichen Implementierungsaufwand sorgt, sodass man seitens der Java-VM-Entwickler gerne finally
sowenig wie möglich nutzen möchte.
Das mit Java 7 eingeführte try-with-resources Statement erlaubt es, Ressourcen, die am Ende eines try
-catch
-Blocks geschlossen werden sollen, einfach im Kopf des Blocks anzugeben. Der Java-Compiler kümmert sich dann darum, dass sie korrekt geschlossen werden und zwar unabhängig davon, ob ein Fehler aufgetreten ist oder nicht.
Ohne die Erweiterung hätte man obiges Beispiel wie folgt programmieren müssen:
FileInputStream fis = new FileInputStream("/tmp/file1");
FileOutputStream fos = new FileOutputStream("/tmp/file2");
try {
int i = fis.read();
fos.write(i);
}
catch (IOException e) {
// Ausnahme behandeln
}
finally {
try {
fis.close();
}
catch (IOException e) { /* ignore */ }
try {
fos.close();
}
catch (IOException e) { /* ignore */ }
}