Java try-with-resources
Chiudi automaticamente le risorse AutoCloseable in Java con l'istruzione try-with-resources.
L'istruzione try-with-resources è la soluzione Java al pattern "apri, usa, chiudi sempre". Si dichiara una risorsa all'inizio del try, e la JVM garantisce che verrà chiusa all'uscita del blocco — che il blocco sia terminato normalmente, abbia lanciato un'eccezione, o sia stato abbandonato per qualsiasi altro motivo di controllo del flusso. L'ingombrante danza con finally si riduce a una singola dichiarazione.
Questo capitolo tratta la sintassi, cosa si qualifica come risorsa, come vengono chiuse più risorse, il meccanismo delle eccezioni soppresse che rende l'istruzione affidabile, e quando non usarla.
La forma
try (FileReader reader = new FileReader(path)) {
// use reader
}Tutto qui. Niente finally, niente close() esplicito. Quando il try termina, la JVM chiama reader.close() automaticamente. Qualsiasi clausola catch e finally aggiunta funziona comunque — viene eseguita dopo la chiusura della risorsa.
try (FileReader reader = new FileReader(path)) {
// use reader
} catch (IOException e) {
// handle — at this point the reader is already closed
}Cosa può essere una risorsa
Una risorsa deve implementare AutoCloseable (o la sua più vecchia sottointerfaccia Closeable). Quell'interfaccia dichiara un unico metodo:
public interface AutoCloseable {
void close() throws Exception;
}La maggior parte dei tipi che si usano comunemente la implementa già: InputStream, OutputStream, Reader, Writer, Connection, Statement, ResultSet, Scanner, wrapper per Lock e molti client di terze parti. Se si scrive una classe che possiede una risorsa, si implementa AutoCloseable direttamente.
Più risorse
Le dichiarazioni si separano con punti e virgola. Le risorse vengono chiuse in ordine inverso rispetto alla dichiarazione, quindi l'ultima aperta è la prima a essere chiusa:
try (
FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst)
) {
in.transferTo(out);
}
// closes `out` first, then `in`L'ordine inverso è importante perché la seconda risorsa spesso dipende dalla prima. Chiudere prima quella dipendente lascia la base intatta per un momento in più, il che è l'ordine più sicuro.
Riutilizzo di risorse esistenti (Java 9+)
Se si ha già una variabile che contiene un AutoCloseable, non è necessario dichiararne una nuova — la si passa per nome:
BufferedReader reader = openSomewhere();
try (reader) {
// use it
}La variabile deve essere final o effettivamente final (non si può riassegnarla). Questa forma è utile quando una risorsa viene costruita altrove — al costo di rendere meno visibile la chiusura. Va usata quando la variabile esistente è proprio lì; altrimenti è preferibile la dichiarazione inline.
Eccezioni soppresse
Ecco la parte sottile. Cosa succede se il corpo del try lancia un'eccezione e anche la chiamata a close() ne lancia una? Prima di Java 7, l'eccezione di chiusura del finally avrebbe sostituito quella originale — perdendo così la causa reale.
try-with-resources risolve questo problema. L'eccezione originale è quella lanciata al chiamante; qualsiasi eccezione generata durante la chiusura viene allegata come eccezione soppressa, recuperabile tramite Throwable.getSuppressed():
try (Resource r = new Resource()) {
r.use(); // throws A
} // close() throws B
// caller sees A
// A.getSuppressed() returns [B]Il printStackTrace() predefinito stampa anche le eccezioni soppresse (righe Suppressed: ... sotto quella principale). Quasi non è mai necessario gestirlo manualmente — il linguaggio preserva la catena per te.
Una forma reale tipica
Un pattern comune, mettendo tutto insieme:
public List<String> readAllLines(Path p) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(p)) {
return reader.lines().toList();
}
}Cose da notare: nessuna chiusura esplicita, nessun finally, il corpo può fare return direttamente, e il chiamante riceve comunque una IOException pulita se qualcosa è andato storto.
Quando try-with-resources è lo strumento sbagliato
È lo strumento giusto quando si possiede l'intero ciclo di vita della risorsa per un blocco. È sbagliato quando:
- La risorsa vive più a lungo del blocco — ad esempio, una connessione mantenuta in cache tra le chiamate. Chiuderla alla fine del metodo interromperebbe il prossimo utente.
- Non si possiede la risorsa — un chiamante l'ha passata. La chiuderà lui stesso.
In questi casi, lasciare la chiusura al proprietario. Se non si riesce a capire chi sia il proprietario, è un odore di design — bisogna scoprirlo prima di scrivere il codice.
Un esempio pratico
Un breve programma che copia una stringa in una pipe in memoria e la rilegge attraverso un buffered reader. Entrambi gli stream sono AutoCloseable; entrambi vengono chiusi automaticamente; viene aggiunta una piccola risorsa personalizzata per vedere l'ordine di chiusura nell'output.
Nell'output si può vedere che l'ordine di dichiarazione è a, b, src, buf ma l'ordine di chiusura è inverso: buf, src, b, a. Questa è la garanzia del linguaggio — il primo aperto, l'ultimo chiuso.
Cosa c'è dopo
Finora abbiamo solo gestito eccezioni lanciate da Java. Il codice reale deve anche sollevarne di proprie — per argomenti non validi, invarianti violate o errori di dominio. Continua con Java throw e throws.