Chiusura Automatica dei File in Java
- L'istruzione
try
con risorse in Java consente di gestire automaticamente la chiusura dei file e altre risorse, prevenendo memory leak. - Questa istruzione è disponibile a partire da JDK 7 e semplifica la gestione delle risorse, come gli stream di file.
- Le risorse dichiarate nell'istruzione
try
sono automaticamente chiuse al termine del blocco, senza necessità di chiamare esplicitamente il metodoclose()
. - È possibile gestire più risorse in un'unica istruzione
try
, separando le dichiarazioni delle risorse con un punto e virgola.
Chiusura Automatica di un File: try
con Risorse
Nella lezione precedente, i programmi di esempio hanno effettuato chiamate esplicite al metodo close()
per chiudere un file una volta che non è più necessario.
Come menzionato, questo è il modo in cui i file venivano chiusi quando si usavano versioni di Java precedenti a JDK 7. Sebbene questo approccio sia ancora valido e utile, JDK 7 ha aggiunto una funzionalità che offre un altro modo per gestire le risorse, come gli stream di file, automatizzando il processo di chiusura.
Questa funzionalità, a volte chiamata gestione automatica delle risorse, o ARM in breve, è basata su una versione espansa dell'istruzione try
. Il vantaggio principale della gestione automatica delle risorse è che previene situazioni in cui un file (o un'altra risorsa) non viene inavvertitamente rilasciato dopo che non è più necessario.
Come spiegato, dimenticare di chiudere un file può risultare in memory leak e potrebbe portare ad altri problemi.
La gestione automatica delle risorse è basata su una forma espansa dell'istruzione try
. Ecco la sua forma generale:
try (dichiarazione_risorsa) {
// usa la risorsa
}
Tipicamente, dichiarazione_risorsa
è un'istruzione che dichiara e inizializza una risorsa, come uno stream di file.
Consiste in una dichiarazione di variabile in cui la variabile è inizializzata con un riferimento all'oggetto che viene gestito. Quando il blocco try
termina, la risorsa viene automaticamente rilasciata. Nel caso di un file, questo significa che il file viene automaticamente chiuso. Quindi, non c'è bisogno di chiamare close()
esplicitamente.
Naturalmente, questa forma di try
può anche includere clausole catch
e finally
. Questa forma di try
è chiamata istruzione try-with-resources
.
try
con risorse precedentemente inizializzate
A partire da JDK 9, è anche possibile che la specificazione delle risorse del try
consista in una variabile che è stata dichiarata e inizializzata in precedenza nel programma.
Tuttavia, quella variabile deve essere effettivamente finale, il che significa che non le è stato assegnato un nuovo valore dopo aver ricevuto il suo valore iniziale.
In altre parole, si deve trattare di una costante.
L'istruzione try
con risorse può essere usata solo con quelle risorse che implementano l'interfaccia AutoCloseable
definita da java.lang
. Questa interfaccia definisce il metodo close()
. AutoCloseable
è ereditata dall'interfaccia Closeable
in java.io
.
Entrambe le interfacce sono implementate dalle classi di stream. Quindi, try
con risorse può essere usato quando si lavora con gli stream, inclusi gli stream di file.
Come primo esempio di chiusura automatica di un file, ecco una versione rielaborata del programma MostraFile
che la utilizza:
/*
* Questa versione del programma MostraFile
* usa un'istruzione try-con-risorse
* per chiudere automaticamente un file
* dopo che non è più necessario.
*/
import java.io.*;
class MostraFile {
public static void main(String[] args) {
int i;
// 1. Verifica che sia stato passato un argomento
if (args.length != 1) {
System.out.println("Uso: MostraFile nomefile");
return;
}
// 2. Il seguente codice usa un'istruzione
// try-con-risorse per aprire un file e
// poi chiuderlo automaticamente quando
// il blocco try viene lasciato.
try (FileInputStream streamIngresso =
new FileInputStream(args[0])) {
do {
i = streamIngresso.read();
if (i != -1)
System.out.print((char) i);
} while (i != -1);
} catch (FileNotFoundException e) {
System.out.println("File Non Trovato.");
} catch (IOException e) {
System.out.println("Si è Verificato un Errore I/O");
}
}
}
Nel programma, si presti particolare attenzione a come il file viene aperto all'interno dell'istruzione try
:
try (FileInputStream streamIngresso =
new FileInputStream(args[0])) {
// ...
}
Si noti come la porzione di specificazione della risorsa del try
dichiara un FileInputStream
chiamato streamIngresso
, che viene poi assegnato a un riferimento al file aperto dal suo costruttore.
Quindi, in questa versione del programma, la variabile streamIngresso
è locale al blocco try
, viene creata quando si entra nel blocco try
.
Quando si esce dal blocco try
, il flusso associato con streamIngresso
viene automaticamente chiuso da una chiamata implicita a close()
. Non si ha bisogno di chiamare close()
esplicitamente, il che significa che non ci si può dimenticare di chiudere il file. Questo è un vantaggio chiave dell'uso di try
con risorse.
È importante capire che una risorsa dichiarata nell'istruzione try
è implicitamente final
. Questo significa che non si può riassegnare la risorsa dopo che è stata creata. Inoltre, l'ambito della risorsa è limitato all'istruzione try
con risorse.
Inferenza dei Tipi e try
con Risorse
Prima di procedere è utile menzionare che a partire da JDK 10, si può adoperare l'inferenza del tipo di variabile locale per specificare il tipo della risorsa dichiarata in un'istruzione try
con risorse. Per farlo, bisogna specificare il tipo come var
. Quando questo viene fatto, il tipo della risorsa viene inferito dal suo inizializzatore.
Per esempio, l'istruzione try
nel programma precedente può ora essere scritta così:
try (var streamIngresso = new FileInputStream(args[0])) {
// ...
}
Qui, streamIngresso
viene inferito essere di tipo FileInputStream
perché quello è il tipo del suo inizializzatore.
Gestire Più Risorse con un'Unica Istruzione try
Si può gestire più di una risorsa all'interno di una singola istruzione try. Per farlo, semplicemente basta separare ogni specificazione di risorsa con un punto e virgola.
Il seguente programma mostra un esempio. Rielabora il programma CopiaFile
mostrato nella lezione precedente così che usa una singola istruzione try
con risorse per gestire sia streamIngresso
che streamUscita
.
/*
* Una versione di CopiaFile che usa try-con-risorse.
* Dimostra due risorse (in questo caso file) che vengono
* gestite da una singola istruzione try.
*/
/*
* Copia un file.
* Per utilizzare questo programma, specificare il nome
* del file sorgente e del file di destinazione.
* Ad esempio, per copiare un file chiamato PRIMO.TXT
* in un file chiamato SECONDO.TXT, utilizzare la seguente
* riga di comando:
*
* java CopiaFile PRIMO.TXT SECONDO.TXT
*/
import java.io.*;
class CopiaFile {
public static void main(String[] args) throws IOException {
int i;
// 1. Verifica che siano stati specificati
// due nomi di file come argomenti della riga di comando.
// Se non sono stati specificati, mostra un messaggio di errore
// e termina il programma.
if (args.length != 2) {
System.out.println("Uso: CopiaFile sorgente destinazione");
return;
}
// Copia il file da args[0] a args[1].
// Apre e gestisce due file tramite l'istruzione try.
try (FileInputStream finput = new FileInputStream(args[0]);
FileOutputStream foutput = new FileOutputStream(args[1])) {
// 2. Copia il contenuto del file sorgente in quello di destinazione.
// Questa parte legge il file sorgente byte per byte
// e scrive ogni byte nel file di destinazione.
do {
i = finput.read();
if (i != -1)
foutput.write(i);
} while (i != -1);
} catch (IOException e) {
System.out.println("Errore I/O: " + e);
}
}
}
In questo programma, si noti come i file di input e output vengono aperti all'interno del blocco try
:
try (FileInputStream finput = new FileInputStream(args[0]);
FileOutputStream foutput = new FileOutputStream(args[1])) {
// ...
}
Dopo che questo blocco try
termina, sia finput
che foutput
saranno stati chiusi. Se si confronta questa versione del programma con la versione precedente, si noterà che è molto più breve. La capacità di semplificare il codice sorgente è un beneficio collaterale della gestione automatica delle risorse.
Eccezioni Soppresse
C'è un altro aspetto del try
con risorse che deve essere menzionato. In generale, quando un blocco try
viene eseguito, è possibile che un'eccezione all'interno del blocco try
porti a un'altra eccezione che si verifica quando la risorsa viene chiusa in una clausola finally
.
Nel caso di un'istruzione try
"normale", l'eccezione originale viene persa, essendo sostituita dalla seconda eccezione. Tuttavia, quando si usa try
con risorse, la seconda eccezione è soppressa. Non è, tuttavia, persa. Invece, viene aggiunta alla lista delle eccezioni soppresse associate alla prima eccezione. La lista delle eccezioni soppresse può essere ottenuta usando il metodo getSuppressed()
definito da Throwable
.