"Amigo, dieci capanne!"

"Sono felice di imparare Java, Capitano!"

"A tuo agio, Amigo. Oggi abbiamo un argomento super interessante. Parleremo di come un programma Java interagisce con risorse esterne e studieremo un'istruzione Java molto interessante. Meglio non coprirsi le orecchie."

"Sono tutto orecchie."

"Mentre un programma Java viene eseguito, a volte interagisce con entità esterne alla macchina Java. Ad esempio, con file su disco. Queste entità sono solitamente chiamate risorse esterne."

"Allora cosa sono considerate risorse interne?"

"Le risorse interne sono gli oggetti creati all'interno della macchina Java. Tipicamente, l'interazione segue questo schema:

Istruzione try-with-resources

"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ò utilizzarla. Se una risorsa è libera, qualsiasi programma può acquisire Esso.

"Immagina che un ufficio abbia condiviso tazze da caffè. 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."

"Capito. È come i posti in metropolitana o altri mezzi di trasporto pubblico. Se un posto è libero, allora chiunque può prenderlo. Se un posto è occupato, allora è controllato dalla persona che lo ha preso."

"Esatto. E ora parliamo dell'acquisizione di 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 è libera, allora la macchina Java acquisisce Esso.

"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à ad essere tenuto dal vostro programma."

"Sembra giusto."

"Per mantenerlo in questo modo, 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.

"È come i programmi che possono consumare tutta la memoria..."

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

"Se questa è la buona notizia, significa che ci sono cattive notizie?"

"Proprio così. La cattiva notizia è che se stai scrivendo un'applicazione server..."

"Ma scrivo queste applicazioni?"

"Molte applicazioni server sono scritte in Java, quindi molto probabilmente le scriverai per lavoro. Come dicevo, se stai scrivendo un'applicazione server, allora il tuo server deve funzionare ininterrottamente per giorni, settimane, mesi, eccetera."

"In altre parole, il programma non termina e ciò significa che la memoria non viene rilasciata automaticamente."

"Esattamente. 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."

"Sono ben pochi mesi di lavoro stabile! Cosa si può fare?"

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

"Ecco 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 più o meno 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

"Ah... Quindi, dopo aver lavorato con un file (o altre risorse esterne), devo chiamare il close()metodo sull'oggetto collegato alla risorsa esterna."

"Sì. Sembra tutto semplice. Ma possono verificarsi eccezioni durante l'esecuzione di un programma e la risorsa esterna non verrà rilasciata."

"E questo è molto brutto. Cosa fare?"

"Per assicurarci che il metodo close()venga sempre chiamato, 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();
}

"Hmm... c'è qualcosa che non va?"

"Giusto. 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();
}

"Adesso va tutto bene?"

"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();
}

"E adesso funziona tutto?"

"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, le nostre 3 righe di codice diventano 10. Ma fondamentalmente abbiamo appena aperto il file e scritto 1."

"Uff... È una buona cosa che conclude la questione. Relativamente comprensibile, ma un po' noioso, no?"

"Così è. Ecco perché i creatori di Java ci hanno aiutato aggiungendo un po' di zucchero sintattico. Ora passiamo al clou del programma, o meglio, a questa lezione:

try-con-risorse

"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."

"Sembra promettente!"

"Il caso generale sembra abbastanza semplice:

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

"Quindi questa è un'altra variazione dell'affermazione try ? "

"Sì. È 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);
}

"Fantastico! Il codice che utilizza try-with-resources è molto più breve e più facile da leggere. E meno codice abbiamo, minori sono le possibilità di commettere errori di battitura o di altro genere."

"Sono contento che ti piaccia. A proposito, possiamo aggiungere catche finallyblocchi all'istruzione try-with-resources. Oppure non puoi aggiungerli se non sono necessari.

Più variabili contemporaneamente

"Spesso potresti incontrare una situazione in cui devi aprire più file contemporaneamente. Supponiamo che tu stia 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 ti consente di creare uno ma più oggetti al suo interno. Il codice che crea gli oggetti deve essere separato da punti e virgola. Ecco l'aspetto generale di questa istruzione:

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

Esempio di copia di file:

Codice corto Codice lungo
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);
}
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();
}

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

"Quello che possiamo dire è che dovremmo usarlo."