W3docs

Java finally-Block

Bereinigungscode in Java mit finally-Blöcken ausführen, die immer ablaufen – ob eine Ausnahme ausgelöst wurde oder nicht.

Ein finally-Block wird ausgeführt, egal wie der try-Block endet — bei normalem Abschluss, abgefangener Ausnahme, nicht abgefangener Ausnahme oder sogar bei einem vorzeitigen return. Diese Garantie ist sein eigentlicher Zweck: Hier platziert man den Bereinigungscode, der auf jeden Fall ausgeführt werden muss. Das Schließen eines Datei-Handles, das Freigeben einer Sperre, das Wiederherstellen eines Thread-Zustands — alles, dessen Fehlen das Programm in einem schlechteren Zustand hinterlassen würde als zuvor.

Die Struktur

try {
  // risky code
} catch (SomeException e) {
  // optional — zero or more catches
} finally {
  // always runs after the try (and any matching catch)
}

finally kann mit catch kombiniert werden, ganz ohne catch (try { ... } finally { ... }), oder mit mehreren catches. Die Teile lassen sich frei kombinieren.

Was „wird immer ausgeführt" bedeutet

Ein finally-Block wird ausgeführt, wenn die Kontrolle den try-Block verlässt, unabhängig davon, wie dies geschieht:

  • Normales Ende des try-Blocks — finally läuft nach der letzten Anweisung.
  • Eine aus try geworfene Ausnahmefinally läuft, nachdem das passende catch abgeschlossen ist (oder, wenn kein catch passt, kurz bevor die Ausnahme weiterpropagiert).
  • return innerhalb von try oder catchfinally läuft bevor der return tatsächlich wirksam wird.
  • break oder continue, das den try-Block verlassen würdefinally läuft vor dem Sprung.

Die einzigen Möglichkeiten, finally zu überspringen, sind: die JVM selbst stirbt (System.exit, Stromausfall, Runtime.halt), eine Endlosschleife oder ein Deadlock innerhalb des try, oder Thread.stop (das aus genau diesem Grund als veraltet gilt). Für alles, was man in normalem Anwendungscode schreibt, ist finally eine harte Garantie.

try {
  return computeAnswer();    // even though there's a return here,
} finally {
  cleanup();                 // this runs before the method actually returns
}

Wofür finally gedacht ist

Die ehrliche Antwort lautet: Ressourcenbereinigung, fast immer. Bevor Java 7 try-with-resources einführte, war die kanonische Form:

InputStream in = null;
try {
  in = new FileInputStream(path);
  // read from in...
} catch (IOException e) {
  // handle
} finally {
  if (in != null) {
    try { in.close(); } catch (IOException ignored) { /* */ }
  }
}

Dieses verschachtelte try/catch um close() im finally-Block ist genau jenes Rauschen, das try-with-resources beseitigen sollte. Das sehen wir im nächsten Kapitel. Aber zu verstehen, wofür finally gedacht ist, macht das neuere Konstrukt verständlich.

Über Ressourcen hinaus ist finally nützlich für:

  • Wiederherstellen von gemeinsamem Zustand, den man für die Dauer des try verändert hat — Erhöhen eines Tiefenzählers, Umschalten eines Flags, Austauschen eines ThreadLocal.
  • Freigeben manuell erworbener Sperren (Lock.lock()try { ... } finally { lock.unlock(); }).
  • Stoppen von Timern oder Schließen von Transaktionen, die AutoCloseable nicht implementieren.

Wofür finally nicht gedacht ist

Keine Logik schreiben, die Ergebnisse in finally produziert. Der Block läuft unabhängig vom Ergebnis — er weiß nicht, ob der try erfolgreich war. Wenn man commit() in finally platziert, wird auch bei einem Fehler committed.

Und kein return aus finally. Das ist eine der wirklich gefährlichen Ecken der Sprache:

try {
  return 1;
} finally {
  return 2;     // wins — the method returns 2 and the original return is lost
}

Das return (oder throw) innerhalb von finally überschreibt jedes return oder jede Ausnahme aus dem try. Die Ausnahme, die gerade weiterpropagieren sollte, wird stillschweigend verworfen. Die meisten Linter markieren dies deshalb als Fehler. Die Regel: finally führt Bereinigung durch; finally produziert keine Werte.

Ausführungsreihenfolge

Wenn sowohl ein catch als auch ein finally vorhanden sind:

try   { ... }
catch { ... }
finally { ... }

Die Reihenfolge ist genau die, die man erwarten würde: try läuft, wenn es eine Ausnahme wirft und ein catch passt, läuft dieses catch, und finally läuft danach — nach welchem auch immer. Wenn finally dann wirft, ersetzt dieser neue Wurf alles, was gerade aus dem try oder catch propagierte — ein weiterer Grund, finally ruhig zu halten.

Ein ausgearbeitetes Beispiel

Wir instrumentieren eine kleine „Transaktion" mit try/catch/finally und rufen sie auf drei verschiedene Arten auf: normaler Erfolg, ein behebbarer Fehler und ein nicht behebbarer. finally läuft in allen drei Fällen — das ist der ganze Sinn.

java— editable, runs on the server

Beim dritten Aufruf wirft doWork eine RuntimeException, die das lokale catch nicht abfängt. finally läuft trotzdem und gibt „release lock" aus, bevor die Ausnahme weiter zu main propagiert. Das ist die Eigenschaft, die man von Bereinigungscode erwartet — er hängt nicht davon ab, ob die Behandlung erfolgreich war.

Was als nächstes kommt

Das Muster „Ressource öffnen, damit arbeiten, im finally schließen" ist so häufig, dass Java eine eigene Anweisung dafür eingebaut hat. Weiter zu Java try-with-resources.

Übungen

Übung
Was gibt diese Methode zurück?\n\n```java\nstatic int demo() {\n try {\n return 1;\n } finally {\n return 2;\n }\n}\n```
Was gibt diese Methode zurück?\n\n```java\nstatic int demo() {\n try {\n return 1;\n } finally {\n return 2;\n }\n}\n```
Was this page helpful?