1. Risorse esterne

Durante l'esecuzione di un programma Java, a volte interagisce con entità esterne alla macchina Java. Ad esempio, con file su disco. Queste entità sono solitamente chiamate risorse esterne. Le risorse interne sono gli oggetti creati all'interno della macchina Java.

Tipicamente, l'interazione segue questo schema:

Istruzione try-with-resources

Monitoraggio delle risorse

Il sistema operativo tiene rigorosamente traccia delle risorse disponibili e controlla anche l'accesso condiviso ad esse da diversi programmi. Ad esempio, se un programma modifica un file, un altro programma non può modificare (o eliminare) quel file. Questo principio non è limitato ai file, ma forniscono l'esempio più facilmente comprensibile.

Il sistema operativo dispone di funzioni (API) che consentono a un programma di acquisire e/o rilasciare risorse. Se una risorsa è occupata, solo il programma che l'ha acquisita può lavorarci. Se una risorsa è gratuita, qualsiasi programma può acquisirla.

Immagina che il tuo ufficio abbia tazze da caffè condivise. Se qualcuno prende una tazza, le altre persone non possono più prenderla. Ma una volta che la tazza è stata usata, lavata e rimessa al suo posto, chiunque può riprenderla. La situazione con i posti su un autobus o in metropolitana è la stessa. Se un posto è libero, chiunque può prenderlo. Se un posto è occupato, è controllato dalla persona che lo ha occupato.

Acquisire risorse esterne .

Ogni volta che il tuo programma Java inizia a lavorare con un file su disco, la macchina Java chiede al sistema operativo l'accesso esclusivo ad esso. Se la risorsa è gratuita, la macchina Java la acquisisce.

Ma dopo che hai finito di lavorare con il file, questa risorsa (file) deve essere rilasciata, cioè devi notificare al sistema operativo che non ne hai più bisogno. Se non lo fai, la risorsa continuerà a essere trattenuta dal tuo programma.

Il sistema operativo mantiene un elenco di risorse occupate da ciascun programma in esecuzione. Se il tuo programma supera il limite di risorse assegnato, il sistema operativo non ti fornirà più nuove risorse.

La buona notizia è che se il tuo programma termina, tutte le risorse vengono rilasciate automaticamente (lo fa il sistema operativo stesso).

La cattiva notizia è che se stai scrivendo un'applicazione server (e molte applicazioni server sono scritte in Java), il tuo server deve essere in grado di funzionare per giorni, settimane e mesi senza interruzioni. E se apri 100 file al giorno e non li chiudi, in un paio di settimane la tua applicazione raggiungerà il limite di risorse e andrà in crash. Questo è molto al di sotto di mesi di lavoro stabile.


2. close()metodo

Le classi che utilizzano risorse esterne hanno un metodo speciale per rilasciarle: close().

Di seguito forniamo un esempio di un programma che scrive qualcosa su un file e poi chiude il file quando ha finito, cioè libera le risorse del sistema operativo. Assomiglia a questo:

Codice Nota
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
Il percorso del file.
Ottieni l'oggetto file: acquisisci la risorsa.
Scrivi nel file
Chiudi il file - rilascia la risorsa

Dopo aver lavorato con un file (o altre risorse esterne), è necessario chiamare il close()metodo sull'oggetto collegato alla risorsa esterna.

Eccezioni

Sembra tutto semplice. Ma possono verificarsi eccezioni durante l'esecuzione di un programma e la risorsa esterna non verrà rilasciata. E questo è molto brutto.

Per assicurarci che il metodo venga sempre chiamato close(), dobbiamo avvolgere il nostro codice in un blocco try-- e aggiungere il metodo al blocco. Sarà simile a questo:catchfinallyclose()finally

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Questo codice non verrà compilato, perché la outputvariabile è dichiarata all'interno del try {}blocco, e quindi non è visibile nel finallyblocco.

Risolviamolo:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Va bene, ma non funzionerà se si verifica un errore quando creiamo l' FileOutputStreamoggetto, e questo potrebbe accadere abbastanza facilmente.

Risolviamolo:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Ci sono ancora alcune critiche. Innanzitutto, se si verifica un errore durante la creazione dell'oggetto FileOutputStream, la outputvariabile sarà nulla. Questa possibilità deve essere presa in considerazione nel finallyblocco.

In secondo luogo, il close()metodo viene sempre chiamato nel finallyblocco, il che significa che non è necessario nel tryblocco. Il codice finale sarà simile a questo:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

Anche se non consideriamo il catchblocco, che può essere omesso, allora le nostre 3 righe di codice diventano 10. Ma praticamente abbiamo appena aperto il file e scritto 1. Un po' macchinoso, non trovate?


3. try-con-risorse

E qui i creatori di Java hanno deciso di cospargerci un po' di zucchero sintattico. A partire dalla sua settima versione, Java ha una nuova trydichiarazione -with-resources.

È stato creato proprio per risolvere il problema con la chiamata obbligatoria al close()metodo. Il caso generale sembra abbastanza semplice:

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

Questa è un'altra variazione della try dichiarazione . È necessario aggiungere le parentesi dopo la tryparola chiave, quindi creare oggetti con risorse esterne all'interno delle parentesi. Per ogni oggetto tra parentesi, il compilatore aggiunge una finallysezione e una chiamata al close()metodo.

Di seguito sono riportati due esempi equivalenti:

Codice lungo Codice con try-with-resources
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

Il codice che utilizza try-with-resources è molto più breve e più facile da leggere. E meno codice abbiamo, meno possibilità di commettere errori di battitura o di altro tipo.

A proposito, possiamo aggiungere catche finallyblocchi all'istruzione try-with-resources. Oppure non puoi aggiungerli se non sono necessari.



4. Più variabili contemporaneamente

A proposito, potresti spesso incontrare una situazione in cui devi aprire più file contemporaneamente. Diciamo che stai copiando un file, quindi hai bisogno di due oggetti: il file da cui stai copiando i dati e il file in cui stai copiando i dati.

In questo caso, l' tryistruzione -with-resources consente di creare uno ma più oggetti al suo interno. Il codice che crea gli oggetti deve essere separato da punto e virgola. Ecco l'aspetto generale di questa affermazione:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

Esempio di copia di file:

Codice lungo Codice corto
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

Bene, cosa possiamo dire qui? try-con-risorse è una cosa meravigliosa!


5. AutoCloseableinterfaccia

Ma non è tutto. Il lettore attento inizierà immediatamente a cercare le insidie ​​che limitano l'applicazione di questa affermazione.

Ma come tryfunziona l'istruzione -with-resources se la classe non ha un close()metodo? Bene, supponiamo che non verrà chiamato nulla. Nessun metodo, nessun problema.

Ma come tryfunziona l'istruzione -with-resources se la classe ha diversi close()metodi? E hanno bisogno di argomenti da passare loro? E la classe non ha un close()metodo senza parametri?

Spero davvero che tu ti sia posto queste domande, e forse altre ancora.

Per evitare tali problemi, i creatori di Java hanno ideato un'interfaccia speciale chiamata AutoCloseable, che ha un solo metodo — close(), che non ha parametri.

Hanno anche aggiunto la restrizione che solo gli oggetti delle classi che implementanoAutoCloseable possono essere dichiarati come risorse in tryun'istruzione -with-resources. Di conseguenza, tali oggetti avranno sempre un close()metodo senza parametri.

A proposito, pensi che sia possibile per tryun'istruzione -with-resources dichiarare come risorsa un oggetto la cui classe ha il proprio close()metodo senza parametri ma che non implementa AutoCloseable?

La cattiva notizia: la risposta corretta è no: le classi devono implementare l' AutoCloseableinterfaccia.

La buona notizia: Java ha molte classi che implementano questa interfaccia, quindi è molto probabile che tutto funzioni come dovrebbe.