Java Language La dichiarazione try-with-resources


Esempio

Java SE 7

Come illustra l'esempio di dichiarazione try-catch-final , il cleanup delle risorse che utilizza una clausola finally richiede una quantità significativa di codice "boiler-plate" per implementare correttamente le edge case. Java 7 fornisce un modo molto più semplice per affrontare questo problema nella forma dell'istruzione try-with-resources .

Cos'è una risorsa?

Java 7 ha introdotto l'interfaccia java.lang.AutoCloseable per consentire la gestione delle classi utilizzando l'istruzione try-with-resources . Le istanze di classi che implementano AutoCloseable sono indicate come risorse . Questi in genere devono essere smaltiti in modo tempestivo piuttosto che affidarsi al garbage collector per smaltirli.

L'interfaccia AutoCloseable definisce un singolo metodo:

public void close() throws Exception

Un metodo close() dovrebbe disporre della risorsa in modo appropriato. La specifica afferma che dovrebbe essere sicuro chiamare il metodo su una risorsa che è già stata eliminata. Inoltre, le classi che implementano l' Autocloseable sono fortemente incoraggiate a dichiarare il metodo close() per generare un'eccezione più specifica di Exception , o nessuna eccezione.

Una vasta gamma di classi e interfacce Java standard implementano AutoCloseable . Questi includono:

  • InputStream , OutputStream e le loro sottoclassi
  • Reader , Writer e le loro sottoclassi
  • Socket e ServerSocket e relative sottoclassi
  • Channel e le sue sottoclassi, e
  • il JDBC interfaccia Connection , Statement e ResultSet e le loro sottoclassi.

Anche le classi di applicazioni e di terze parti possono farlo.

La dichiarazione base di prova con la risorsa

La sintassi di un try-with-resources si basa su forme classiche try-catch , try-finally e try-catch-finally . Ecco un esempio di una forma "base"; cioè la forma senza un catch o, finally .

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

Le risorse da gestire sono dichiarate come variabili nella (...) sezione dopo la clausola try . Nell'esempio sopra, dichiariamo un stream variabili di risorsa e lo inizializziamo su PrintStream appena creato.

Una volta che le variabili della risorsa sono state inizializzate, viene eseguito il blocco try . Al termine, stream.close() verrà chiamato automaticamente per garantire che la risorsa non perda. Si noti che la chiamata close() avviene indipendentemente dal completamento del blocco.

Le dichiarazioni avanzate di try-with-resource

L'istruzione try-with-resources può essere migliorata con catch blocchi catch e finally , come con la sintassi pre-Java 7 try-catch-finally . Il seguente frammento di codice aggiunge un blocco catch al precedente per gestire l' PrintStream FileNotFoundException che può essere PrintStream costruttore PrintStream :

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
} catch (FileNotFoundException ex) {
    System.err.println("Cannot open the file");
} finally {
    System.err.println("All done");
}

Se l'inizializzazione della risorsa o il blocco try genera l'eccezione, verrà eseguito il blocco catch . Il blocco finally verrà sempre eseguito, come in una dichiarazione try-catch-finally convenzionale.

Ci sono un paio di cose da notare però:

  • La variabile di risorsa è fuori ambito nel catch e finally blocchi.
  • La pulizia delle risorse avverrà prima che l'istruzione tenti di far corrispondere il blocco catch .
  • Se la pulizia automatica delle risorse ha generato un'eccezione, potrebbe essere catturata in uno dei blocchi catch .

Gestire più risorse

I frammenti di codice sopra mostrano una singola risorsa che viene gestita. In effetti, try-with-resources può gestire più risorse in un'unica istruzione. Per esempio:

try (InputStream is = new FileInputStream(file1);
     OutputStream os = new FileOutputStream(file2)) {
    // Copy 'is' to 'os'
}

Questo si comporta come ti aspetteresti. Sia is che os vengono chiusi automaticamente alla fine del blocco try . Ci sono un paio di punti da notare:

  • Le inizializzazioni si verificano nell'ordine di codice e gli inizializzatori di variabili di risorse successive possono utilizzare i valori di quelli precedenti.
  • Tutte le variabili di risorsa inizializzate correttamente verranno eliminate.
  • Le variabili delle risorse vengono pulite in ordine inverso rispetto alle loro dichiarazioni.

Pertanto, nell'esempio di cui sopra, is è inizializzato prima os e ripulito dopo di essa, e is verrà pulito se c'è un'eccezione durante l'inizializzazione os .

Equivalenza di try-with-resource e try-catch-finally classico

La specifica del linguaggio Java specifica il comportamento delle forme try-with-resource in termini della classica dichiarazione try-catch-finally . (Si prega di fare riferimento al JLS per tutti i dettagli.)

Ad esempio, questa base di prova con risorsa :

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

è definito come equivalente a questo try-catch-finally :

// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");

// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;

// The actual try block
try {
    stream.println("Hello world!");
} catch (Throwable t) {
    // If an exception is thrown, remember it for the finally block
    primaryException = t;
    throw t;
} finally {
    if (primaryException == null) {
        // If no exception was thrown so far, exceptions thrown in close() will
        // not be caught and therefore be passed on to the enclosing code.
        stream.close();
    } else {
        // If an exception has already been thrown, any exception thrown in
        // close() will be suppressed as it is likely to be related to the
        // previous exception. The suppressed exception can be retrieved
        // using primaryException.getSuppressed().
        try {
            stream.close();
        } catch (Throwable suppressedException) {
            primaryException.addSuppressed(suppressedException);
        }
    }
}

(Il JLS specifica che le variabili t e primaryException effettive saranno invisibili al normale codice Java.)

La forma migliorata di try-with-resources è specificata come un'equivalenza con il modulo base. Per esempio:

try (PrintStream stream = new PrintStream(fileName)) {
    stream.println("Hello world!");
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}

è equivalente a:

try {
    try (PrintStream stream = new PrintStream(fileName)) {
        stream.println("Hello world!");
    }
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}