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:
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 |
---|---|
|
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:catch
finally
close()
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 output
variabile è dichiarata all'interno del try {}
blocco, e quindi non è visibile nel finally
blocco.
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' FileOutputStream
oggetto, 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 output
variabile sarà nulla. Questa possibilità deve essere presa in considerazione nel finally
blocco.
In secondo luogo, il close()
metodo viene sempre chiamato nel finally
blocco, il che significa che non è necessario nel try
blocco. 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 catch
blocco, 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 try
dichiarazione -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 try
parola chiave, quindi creare oggetti con risorse esterne all'interno delle parentesi. Per ogni oggetto tra parentesi, il compilatore aggiunge una finally
sezione e una chiamata al close()
metodo.
Di seguito sono riportati due esempi equivalenti:
Codice lungo | Codice con try-with-resources |
---|---|
|
|
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 catch
e finally
blocchi 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' try
istruzione -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 |
---|---|
|
|
Bene, cosa possiamo dire qui? try
-con-risorse è una cosa meravigliosa!
5. AutoCloseable
interfaccia
Ma non è tutto. Il lettore attento inizierà immediatamente a cercare le insidie che limitano l'applicazione di questa affermazione.
Ma come try
funziona 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 try
funziona 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 try
un'istruzione -with-resources. Di conseguenza, tali oggetti avranno sempre un close()
metodo senza parametri.
A proposito, pensi che sia possibile per try
un'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' AutoCloseable
interfaccia.
La buona notizia: Java ha molte classi che implementano questa interfaccia, quindi è molto probabile che tutto funzioni come dovrebbe.
GO TO FULL VERSION