Java: try-with-resources

von Hubert Schmid vom 2013-01-20

Es gibt zwei gute Gründe für die Einführung von try-with-resources mit Java 7: Erstens kümmern sich viele Java-Programme unzureichend um die Ressourcenverwaltung, was sich häufig erst unter produktiver Last bemerkbar macht. Und zweitens ist die Ressourcenverwaltung mit dem älteren Konstrukt try-finally deutlich komplizierter als es auf den ersten Blick aussieht. Java 7 adressiert beide Probleme mit try-with-resources, das sowohl einfach zu verwenden als auch zuverlässig ist. Doch leider gibt es immer noch viel Code, bei dem es nicht verwendet wird.

Nochmals zur Motivation: Die automatische Speicherbereinigung von Java funktioniert sehr gut für den Hauptspeicher. Für die meisten anderen Ressourcen ist sie dagegen praktisch nutzlos. Ein typisches Beispiel sind die Deskriptoren für Dateien, die explizit geschlossen werden müssen.

FileReader in = new FileReader("/path/to/file"); // do something with the open file ... in.close();

In obigem Beispiel wird die Ressource allerdings nur zeitnah freigegeben, wenn bei der Verarbeitung der Datei keine Ausnahme geworfen wird. Dieses potentielle Leak lässt sich mit try-finally schließen, wobei der close-Aufruf in den finally-Block verschoben wird. Einfacher und kürzer geht es mit try-with-resources:

try (FileReader in = new FileReader("/path/to/file")) { // do something with the open file ... }

Der Code macht allerdings mehr als man beim ersten Blick erahnt. Besonders deutlich wird der Unterschied, wenn weitere Ressourcen beteiligt sind. Um das zu zeigen, erweitere ich das Beispiel um eine zweite Datei:

try (FileReader in = new FileReader("/path/to/file"); FileWriter out = new FileWriter("/path/to/another/file")) { // do something with the open files ... }

Dieser harmlos erscheinende Code entspricht ungefähr dem folgenden Code ohne try-with-resources. Die Komplexität ist erforderlich, da während der Behandlung einer Ausnahme weitere Ausnahmen auftreten können. Die beiden wichtigsten und auffälligsten Punkte dabei sind:

Throwable throwable = null; final FileReader in = new FileReader("/path/to/file"); try { final FileWriter out = new FileWriter("/path/to/another/file"); try { // do something with the open files ... } catch (Throwable t) { throwable = t; throw t; } finally { if (throwable == null) { out.close(); } else { try { out.close(); } catch (Throwable t) { throwable.addSuppressed(t); } } } } catch (Throwable t) { throwable = t; throw t; } finally { if (throwable == null) { in.close(); } else { try { in.close(); } catch (Throwable t) { throwable.addSuppressed(t); } } }

Das ist im Wesentlichen der Code, den man haben möchte, um Ressourcen zuverlässig zu verwalten. try-with-resources gibt den Entwicklern die Möglichkeit ihn mit minimalem Programmieraufwand zu realisieren. Jetzt muss man es nur noch nutzen.