CodeGym /Corsi /JAVA 25 SELF /Lavorare con try-with-resources: chiusura automatica dell...

Lavorare con try-with-resources: chiusura automatica delle risorse

JAVA 25 SELF
Livello 36 , Lezione 4
Disponibile

1. Introduzione

Lavorare con i file in Java (e non solo!) significa sempre lavorare con risorse esterne. Quando aprite un file, il sistema operativo assegna al vostro programma un «descrittore» — un identificatore speciale che consente di leggere e scrivere nel file. Il numero di tali descrittori è limitato: se non si chiudono i file, il programma può «mangiarsi» rapidamente tutte le risorse disponibili e cominciare a generare errori misteriosi del tipo "Too many open files".

Inoltre, se un file non viene chiuso, può rimanere bloccato per altri programmi. Per esempio, avete aperto un file in scrittura, vi siete dimenticati di chiuderlo e ora né voi né altri potete modificarlo o eliminarlo. Una sorta di «sequestro eterno di ostaggi» nel mondo dei file system.

Esempio dal mondo reale

FileInputStream fis = new FileInputStream("data.txt");
int b = fis.read();
// ... facciamo qualcosa e poi ci dimentichiamo di fis.close()

Se non si chiama il metodo close(), il file rimarrà «appeso» fino alla conclusione del programma. Nelle applicazioni grandi questo può portare a perdite di risorse e persino al crash dell’applicazione.

2. Metodo vecchio: finally + close()

Fino a Java 7 il modo classico per garantire la chiusura di un file era questo:

FileInputStream fis = null;
try {
    fis = new FileInputStream("data.txt");
    // leggiamo il file
    int b = fis.read();
    // ...
} catch (IOException e) {
    System.out.println("Errore durante la lettura del file: " + e.getMessage());
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            System.out.println("Errore durante la chiusura del file: " + e.getMessage());
        }
    }
}

Svantaggi di questo approccio

  • È facile dimenticare il blocco finally e ritrovarsi con perdite di risorse.
  • Tanto codice superfluo, soprattutto se i flussi sono molti.
  • Se durante la chiusura si verifica un’eccezione, va catturata a parte.
  • Il codice diventa ingombrante e meno leggibile.

3. Approccio moderno: try-with-resources

Per fortuna, in Java 7 è stata introdotta una sintassi che risolve questi problemi in modo elegante e automatico — try-with-resources.

Come appare

try (FileInputStream fis = new FileInputStream("data.txt")) {
    int b = fis.read();
    // lavoriamo con il file
} catch (IOException e) {
    System.out.println("Errore durante l'uso del file: " + e.getMessage());
}
// Qui fis è già chiuso automaticamente!

Punto chiave: tutte le risorse dichiarate tra le parentesi tonde dopo try verranno chiuse automaticamente alla fine del blocco — anche se a metà è stata lanciata un’eccezione. Non serve scrivere finally, né catturare le eccezioni di chiusura a parte — Java farà tutto per voi.

Quali classi si possono usare in try-with-resources?

Qualsiasi classe che implementi l’interfaccia AutoCloseable (o il più tradizionale Closeable). Questo include quasi tutti i flussi standard di I/O: FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, Scanner, PrintWriter e molti altri.

4. Sintassi di try-with-resources: dettagli ed esempi

Una risorsa

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Errore: " + e.getMessage());
}
// reader è chiuso automaticamente!

Più risorse

Si possono dichiarare più risorse separate da punto e virgola:

try (
    BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
} catch (IOException e) {
    System.out.println("Errore durante la copia: " + e.getMessage());
}
// entrambi i flussi sono chiusi!

Ordine di chiusura: le risorse vengono chiuse in ordine inverso rispetto alla loro dichiarazione. Prima verrà chiamato writer.close(), poi reader.close(). Questo è importante se un flusso dipende dall’altro.

Uso con classi personalizzate

Se scrivete una vostra classe che lavora con risorse, implementate semplicemente l’interfaccia AutoCloseable:

class MyResource implements AutoCloseable {
    public void doSomething() {
        System.out.println("Lavoriamo con la risorsa!");
    }

    @Override
    public void close() {
        System.out.println("Risorsa chiusa!");
    }
}

try (MyResource res = new MyResource()) {
    res.doSomething();
}
// All'uscita dal blocco verrà stampato: "Risorsa chiusa!"

5. Come funziona: schema

flowchart TD
    A[Apertura della risorsa in try-with-resources] --> B{Si è verificata un'eccezione nel blocco try?}
    B -- No --> C[La risorsa viene chiusa automaticamente]
    B -- Sì --> D[La risorsa viene chiusa automaticamente]
    D --> E[L'eccezione viene propagata]
    C --> F[Il programma continua l'esecuzione]

Conclusione: indipendentemente dal fatto che si verifichi o meno un errore, la risorsa verrà sempre chiusa!

6. Esempi: riscriviamo il codice «alla maniera nuova»

Prima (stile vecchio):

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("input.txt"));
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Errore: " + e.getMessage());
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            System.out.println("Errore durante la chiusura: " + e.getMessage());
        }
    }
}

Dopo (try-with-resources):

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Errore: " + e.getMessage());
}
// Tutto qui, niente finally!

7. Cosa succede se si verifica un errore durante la chiusura?

A volte l’operazione di chiusura della risorsa può lanciare un’eccezione (per esempio, se il file scompare all’improvviso). In try-with-resources tali eccezioni non vanno perse: se nel blocco try c’è già stata un’eccezione e durante la chiusura ne sorge una seconda, questa verrà aggiunta come «soppressa» (suppressed exception) alla principale. Lo si può vedere con il metodo Throwable.getSuppressed().

Esempio

try (MyResource res = new MyResource()) {
    throw new IOException("Errore nel blocco try");
} catch (IOException e) {
    System.out.println("Errore principale: " + e.getMessage());
    for (Throwable suppressed : e.getSuppressed()) {
        System.out.println("Eccezione soppressa: " + suppressed.getMessage());
    }
}

8. Quali classi supportano try-with-resources?

È semplice: qualsiasi classe che implementi l’interfaccia AutoCloseable. Eccone solo alcune tra le standard:

Classe Scopo
FileInputStream
Lettura di byte da file
FileOutputStream
Scrittura di byte su file
FileReader/FileWriter
Lettura/scrittura di testo
BufferedReader/Writer
Bufferizzazione dei flussi
PrintWriter
Scrittura di testo con formattazione
Scanner
Lettura di dati da file/console
ObjectInputStream/Output
Serializzazione/deserializzazione
ZipInputStream/Output
Lavoro con archivi ZIP
Socket, ServerSocket
Connessioni di rete

Se utilizzate librerie di terze parti, consultate la documentazione: se esiste un metodo close(), molto probabilmente la classe supporta try-with-resources.

9. Consigli e sfumature utili

Si possono dichiarare variabili fuori dal try (da Java 9): si possono usare risorse già dichiarate se sono final o «effectively final»:

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try (reader) {
    // ...
}

Funziona non solo con i file: try-with-resources è comodo per qualsiasi risorsa: connessioni di rete, database, qualunque oggetto con il metodo close().

Non ignorate le eccezioni: anche con try-with-resources ricordate di catturare e gestire le eccezioni — non è una panacea, ma solo un modo comodo per evitare perdite.

Non chiudete la risorsa manualmente dentro il try: non serve — Java farà tutto per voi! Se chiamate close() manualmente e poi termina il blocco try, si tenterà di chiudere una risorsa già chiusa. Di solito è innocuo, ma può confondere.

10. Errori tipici nell’uso di try-with-resources

Errore n. 1: avete dimenticato di usare try-with-resources. Se state ancora scrivendo finally { resource.close(); } — o siete rimasti al 2011, oppure non avete letto questa lezione! Usate la sintassi moderna.

Errore n. 2: la risorsa è dichiarata fuori dal try e dentro viene solo usata. Un codice del genere non chiuderà automaticamente la risorsa:

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try {
    // ... usiamo reader
} finally {
    // E qui ci si è dimenticati di chiuderlo!
}

Errore n. 3: chiamate close() manualmente dentro il blocco try. Non è critico, ma è ridondante e può portare a una doppia chiusura. Fidatevi di Java.

Errore n. 4: catturate solo Exception, ignorando la specificità di IO. Meglio catturare eccezioni specifiche (FileNotFoundException, IOException) per fornire messaggi chiari all’utente.

Errore n. 5: non gestite le eccezioni soppresse. Se durante la chiusura della risorsa si verifica un errore, questo può essere «soppresso». Se analizzate gli errori, non dimenticate getSuppressed().

1
Sondaggio/quiz
Lettura e scrittura di file, livello 36, lezione 4
Non disponibile
Lettura e scrittura di file
Lettura e scrittura di file
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION