WirWissenWasWirTun™

AutoCloseable: Eine subtile Vertragsänderung zwischen Java 7 und Java 8

Lukas Eder

© Shutterstock.com/Franck Boston

Wenn Sie sowohl Java 7 als auch Java 8 kennen, so haben Sie vielleicht mitbekommen, dass sich für den Typ AutoCloseable in der neuesten Java-Version eine kleine Änderung ergeben hat. Lukas Eder erläutert, in welcher Hinsicht der AutoCloseable-Vertrag nun dem Iterable-Vertrag ähnelt.

Ein nettes Feature des Java-7-Statements try-with-resources und des Typs AutoCloseable – der im Rahmen des Statements eingeführt wurde – ist der Umstand, dass statische Codeanalyse-Tools Ressourcen-Lecks entdecken können. Zum Beispiel in Eclipse:

eder-resource-leak

Versucht man mit der obenstehenden Konfiguration das folgende Programm auszuführen, so erhält man drei Warnungen:

public static void main(String[] args)
throws Exception {
    Connection c = DriverManager.getConnection(
         "jdbc:h2:~/test", "sa", "");
    Statement s = c.createStatement();
    ResultSet r = s.executeQuery("SELECT 1 + 1");
    r.next();
    System.out.println(r.getInt(1));
}

Der Output ist trivial:

2

Die Warnungen werden für c, s und r ausgegeben. Eine schnelle Lösung (bitte nicht nachmachen!) besteht darin, die Warnung mit Hilfe eines Eclipse-spezifischen SuppressWarnings-Parameters zu unterdrücken:

@SuppressWarnings("resource")
public static void main(String[] args)
throws Exception {
    ...
}

Immerhin WissenWirWasWirTun™ und es handelt sich ja bloß um ein Beispiel, richtig?

Falsch!

Die richtige Lösung, selbst für einfache Beispiele, besteht – zumindest im Fall von Java 7 – darin, das leichtgängige Statement try-with-resources zu verwenden.

public static void main(String[] args)
throws Exception {
    try (Connection c = DriverManager.getConnection(
             "jdbc:h2:~/test", "sa", "");
         Statement s = c.createStatement();
         ResultSet r = s.executeQuery("SELECT 1 + 1")) {

        r.next();
        System.out.println(r.getInt(1));
    }
}

Tatsächlich wäre es wunderbar, wenn Eclipse diese Warnungen automatisch fixen und alle einzelnen Statements in einem try-with-resources-Statement verpacken würde (Diese Featureanfrage bitte upvoten!).

Lesen Sie auch: Top 10 der nützlichen, aber paranoiden Java-Programmiertechniken

Toll, das wissen wir. Aber was ist mit Java 8?

In Java 8 hat sich der AutoCloseable-Vertrag in sehr subtiler – oder, je nach Standpunkt: grober – Hinsicht verändert.

Die Java-7-Version

A resource that must be closed when it is no longer needed.

Beachten Sie bitte das Wörtchen „must“ – muss!

Die Java-8-Version

An object that may hold resources (such as file or socket handles) until it is closed. The close() method of an AutoCloseable object is called automatically when exiting a try-with-resources block for which the object has been declared in the resource specification header. This construction ensures prompt release, avoiding resource exhaustion exceptions and errors that may otherwise occur.

API Note:

It is possible, and in fact common, for a base class to implement AutoCloseable even though not all of its subclasses or instances will hold releasable resources. For code that must operate in complete generality, or when it is known that the AutoCloseable instance requires resource release, it is recommended to use try-with-resources constructions. However, when using facilities such as Stream that support both I/O-based and non-I/O-based forms, try-with-resources blocks are in general unnecessary when using non-I/O-based forms.

Kurz gesagt: Ab Java 8 ist AutoCloseable eher ein Hinweis darauf, dass man eventuell eine Ressource verwendet, die geschlossen werden muss – aber das ist nicht notwendigerweise der Fall.

Damit ähnelt AutoCloseable dem Iterable-Vertrag: Dieser stellt nicht klar, ob man über Iterable nur einmal oder mehrmals iterieren kann, zwingt jedoch einen Vertrag auf, der für die foreach-Schleife erforderlich ist.

Wann haben wir „optional schließbare“ Ressourcen?

Nehmen wir zum Beispiel jOOQ. Im Gegensatz zu JDBC kann eine jOOQ-Abfrage (die ab jOOQ 3.7 AutoCloseable ist) eine Ressource repräsentieren oder auch nicht, je nachdem, wie man sie ausführt. Standardmäßig ist sie keine Ressource:

try (Connection c = DriverManager.getConnection(
        "jdbc:h2:~/test", "sa", "")) {

    // No new resources created here:
    ResultQuery<Record> query =
        DSL.using(c).resultQuery("SELECT 1 + 1");

    // Resources created and closed immediately
    System.out.println(query.fetch());
}

Der Output ist erneut:

+----+
|   2|
+----+
|   2|
+----+

Wieder haben wir eine Eclipse-Warnung für die query-Variable, die uns sagt, dass eine Ressource geschlossen werden muss. Doch selbst wenn man jOOQ auf diese Art verwendet, wissen wir diesmal, dass das nicht stimmt. Die einzige Ressource in dem obenstehenden Code ist die JDBC-Connection, und die wird korrekt gehandhabt. Das jOOQ-interne PreparedStatement und ResultSet werden vollständig abgewickelt und von jOOQ eifrig geschlossen.

Warum AutoCloseable dann überhaupt implementieren?

jOOQ kehrt das Standardverhalten von JDBC um:

  • In JDBC wird standardmäßig alles lax gehandhabt und Ressourcen müssen explizit geschlossen werden.
  • In jOOQ wird standardmäßig alles eifrig gehandhabt und Ressourcen können – optional – explizit am Leben gehalten werden.

Der folgende Code beispielsweise sorgt für ein offenes PreparedStatement und ResultSet:

try (Connection c = DriverManager.getConnection(
        "jdbc:h2:~/test", "sa", "");

     // We "keep" the statement open in the ResultQuery
     ResultQuery<Record> query =
         DSL.using(c)
            .resultQuery("SELECT 1 + 1")
            .keepStatement(true)) {

    // We keep the ResultSet open in the Cursor
    try (Cursor<Record> cursor = query.fetchLazy()) {
        System.out.println(cursor.fetchOne());
    }
}

Mit dieser Version haben wir in Eclipse keinerlei Warnungen mehr zu erwarten, doch die obenstehende Version ist bei Verwendung des jOOQ-API wirklich die Ausnahme.

Das gleiche gilt für das Stream-API von Java 8. Interessanterweise gibt Eclipse hier keinerlei Warnungen aus:

Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
stream.forEach(System.out::println);

Fazit

Die Entdeckung von Ressourcen-Lecks scheint auf den ersten Blick eine tolles IDE-/Compiler-Feature zu sein. Die Vermeidung von falschen Positiven gestaltet sich jedoch schwierig. Insbesondere weil Java 8 die AutoCloseable-Verträge geändert hat, ist die Implementierung des AutoCloseable-Vertrags zu reinen Bequemlichkeitszwecken möglich, anstatt als Hinweis darauf zu dienen, dass eine vorhandene Ressource geschlossen werden MUSS.

Dadurch wird es für eine IDE sehr schwer – wenn nicht gar unmöglich – Ressourcen-Lecks in den APIs von Drittanbietern (also nicht-JDK-APIs), wo diese Verträge im allgemeinen nicht sehr bekannt sind, zu finden. Wie so oft im Zusammenhang mit statischen Codeanalyse-Tools besteht die Lösung darin, die Suche nach Ressourcen-Lecks zu deaktivieren:

eder-resource-leak-solution

Um einen tieferen Einblick zu gewinnen bietet sich auch diese Antwort von Stuart Marks auf Stack Overflow an, die an die EG-Diskussionen über lambda-dev anknüpft.

Aufmacherbild: sorry we are closed von Shutterstock.com / Urheberrecht: Franck Boston

Geschrieben von
Lukas Eder
Lukas Eder
Lukas Eder ist leidenschaftlicher Java- und SQL-Entwickler. Er ist Gründer der Data Geekery GmbH, dem Unternehmen hinter jOOQ - die einfachste Art, um SQL in Java zu schreiben.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: