— Amigo, zece-colibă!

— Mă bucur că învăț Java, căpitane!

"La ușurință, Amigo. Astăzi avem un subiect super interesant. Vom vorbi despre modul în care un program Java interacționează cu resursele externe și vom studia o declarație Java foarte interesantă. Mai bine nu-ți acoperi urechile."

„Sunt toată urechile”.

"Pe măsură ce un program Java rulează, uneori interacționează cu entități din afara mașinii Java. De exemplu, cu fișiere de pe disc. Aceste entități sunt de obicei numite resurse externe."

„Atunci ce sunt considerate resurse interne?”

„Resursele interne sunt obiectele create în interiorul mașinii Java. De obicei, interacțiunea urmează această schemă:

Declarație de încercare cu resurse

„Sistemul de operare urmărește cu rigurozitate resursele disponibile și, de asemenea, controlează accesul partajat la acestea din diferite programe. De exemplu, dacă un program modifică un fișier, atunci un alt program nu poate schimba (sau șterge) acel fișier. Acest principiu nu este limitate la fișiere, dar ele oferă exemplul cel mai ușor de înțeles.

„Sistemul de operare are funcții (API-uri) care permit unui program să achiziționeze și/sau să elibereze resurse. Dacă o resursă este ocupată, atunci doar programul care a achiziționat-o poate funcționa cu ea. Dacă o resursă este liberă, atunci orice program poate achiziționa aceasta.

"Imaginați-vă că un birou are căni de cafea în comun. Dacă cineva ia o cană, atunci alții nu o mai pot lua. Dar odată ce cana este folosită, spălată și pusă la loc, atunci oricine o poate lua din nou."

"Am înțeles. Este ca locurile în metrou sau în alt mijloc de transport public. Dacă un loc este liber, atunci oricine îl poate lua. Dacă un loc este ocupat, atunci este controlat de persoana care l-a luat."

„Așa este. Și acum să vorbim despre achiziționarea de resurse externe . De fiecare dată când programul tău Java începe să lucreze cu un fișier de pe disc, mașina Java cere sistemului de operare acces exclusiv la acesta. Dacă resursa este liberă, atunci mașina Java dobândește aceasta.

„Dar după ce ați terminat de lucrat cu fișierul, această resursă (fișier) trebuie eliberată, adică trebuie să anunțați sistemul de operare că nu mai aveți nevoie de ea. Dacă nu faceți acest lucru, atunci resursa va continua să fie deținute de programul tău.”

— Sună corect.

„Pentru a rămâne așa, sistemul de operare menține o listă de resurse ocupate de fiecare program care rulează. Dacă programul tău depășește limita de resurse alocată, atunci sistemul de operare nu îți va mai oferi resurse noi.

„Este ca programele care pot consuma toată memoria...”

„Ceva de genul acesta. Vestea bună este că, dacă programul tău se termină, toate resursele sunt eliberate automat (sistemul de operare însuși face acest lucru).”

„Dacă aceasta este vestea bună, înseamnă asta că există vești proaste?”

"Tocmai așa. Vestea proastă este că dacă scrii o aplicație server..."

— Dar eu scriu astfel de aplicaţii?

„O mulțime de aplicații server sunt scrise în Java, așa că cel mai probabil le vei scrie pentru lucru. După cum spuneam, dacă scrii o aplicație server, atunci serverul tău trebuie să ruleze non-stop zile, săptămâni, luni, etc."

„Cu alte cuvinte, programul nu se termină și asta înseamnă că memoria nu este eliberată automat.”

"Exact. Și dacă deschideți 100 de fișiere pe zi și nu le închideți, atunci în câteva săptămâni aplicația dvs. va atinge limita de resurse și se va prăbuși."

"Asta se încadrează cu mult sub luni de muncă stabilă! Ce se poate face?"

„Clasele care folosesc resurse externe au o metodă specială de eliberare a acestora: close().

„Iată un exemplu de program care scrie ceva într-un fișier și apoi închide fișierul când este gata, adică eliberează resursele sistemului de operare. Arata cam așa:

Cod Notă
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
Calea către fișier.
Obțineți obiectul fișier: obțineți resursa.
Scrieți în fișier
Închideți fișierul - eliberați resursa

"Ah... Deci, după ce lucrez cu un fișier (sau alte resurse externe), trebuie să apelez metoda close()pe obiectul legat de resursa externă."

"Da. Totul pare simplu. Dar pot apărea excepții pe măsură ce un program rulează, iar resursa externă nu va fi eliberată."

"Și asta e foarte rău. Ce să faci?"

„Pentru a ne asigura că close()metoda este întotdeauna apelată, trebuie să ne înfășurăm codul într-un bloc try- catch- finallyși să adăugăm close()metoda la finallybloc. Va arăta cam așa:

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

— Hmm... Ceva nu este în regulă aici?

„Corect. Acest cod nu se va compila, deoarece variabila outputeste declarată în interiorul try{}blocului și, prin urmare, nu este vizibilă în finallybloc.

Hai să o reparăm:

FileOutputStream output = new FileOutputStream(path);

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

„Este totul în regulă acum?”

„Este în regulă, dar nu va funcționa dacă apare o eroare când creăm obiectul FileOutputStream, iar acest lucru s-ar putea întâmpla destul de ușor.

Hai să o reparăm:

FileOutputStream output = null;

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

— Și totul funcționează acum?

„Sunt încă câteva critici. În primul rând, dacă apare o eroare la crearea obiectului FileOutputStream, atunci outputvariabila va fi nulă. Această posibilitate trebuie luată în considerare în finallybloc.

„În al doilea rând, close()metoda este întotdeauna apelată în finallybloc, ceea ce înseamnă că nu este necesară în trybloc. Codul final va arăta astfel:

FileOutputStream output = null;

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

„Chiar dacă nu luăm în considerare blocul catch, care poate fi omis, atunci cele 3 linii de cod ale noastre devin 10. Dar practic am deschis fișierul și am scris 1.”

— Uf... E un lucru bun care încheie chestiunea. Relativ de înțeles, dar oarecum plictisitor, nu-i așa?

„Așa este. De aceea creatorii Java ne-au ajutat adăugând niște zahăr sintactic. Acum să trecem la punctul culminant al programului sau, mai degrabă, la această lecție:

try-cu-resurse

„Începând cu a 7-a versiune, Java are o nouă trydeclarație -with-resources.

„A fost creat tocmai pentru a rezolva problema cu apelul obligatoriu la close()metodă”.

— Sună promițător!

„Cazul general pare destul de simplu:

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

„Deci aceasta este o altă variantă a try afirmației ?”

„Da. Trebuie să adăugați paranteze după trycuvântul cheie și apoi să creați obiecte cu resurse externe în interiorul parantezei. Pentru fiecare obiect din paranteze, compilatorul adaugă o finallysecțiune și un apel la close()metodă.

„Mai jos sunt două exemple echivalente:

Cod lung Codați cu resurse de încercare
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);
}

"Cool! Codul care folosește try-with-resources este mult mai scurt și mai ușor de citit. Și cu cât avem mai puțin cod, cu atât mai puține șanse de a face o greșeală de tipar sau altă eroare."

„Mă bucur că vă place. Apropo, putem adăuga catchși finallybloca trydeclarația -with-resources. Sau nu le puteți adăuga dacă nu sunt necesare.

Mai multe variabile în același timp

„Puteți întâlni adesea o situație în care trebuie să deschideți mai multe fișiere în același timp. Să presupunem că copiați un fișier, deci aveți nevoie de două obiecte: fișierul din care copiați datele și fișierul în care copiați datele .

„În acest caz, tryinstrucțiunea -with-resources vă permite să creați unul, dar mai multe obiecte în ea. Codul care creează obiectele trebuie să fie separat prin punct și virgulă. Iată aspectul general al acestei declarații:

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

Exemplu de copiere a fișierelor:

Cod scurt Cod lung
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();
}

"Ei bine, ce putem spune aici? try-cu-resurse este un lucru minunat!"

„Ceea ce putem spune este că ar trebui să-l folosim”.