"Amigo, dziesiąta chata!"

„Cieszę się, że uczę się Javy, kapitanie!”

„Spokojnie, Amigo. Dzisiaj mamy bardzo ciekawy temat. Porozmawiamy o tym, jak program Java współdziała z zewnętrznymi zasobami i przestudiujemy jedną bardzo interesującą instrukcję Java. Lepiej nie zatykaj uszu”.

"Zamieniam się w słuch."

„Gdy program Java działa, czasami wchodzi w interakcje z podmiotami spoza maszyny Java. Na przykład z plikami na dysku. Te podmioty są zwykle nazywane zasobami zewnętrznymi”.

„Więc co jest uważane za zasoby wewnętrzne?”

„Zasoby wewnętrzne to obiekty tworzone wewnątrz maszyny Java. Zazwyczaj interakcja odbywa się według następującego schematu:

Instrukcja try-with-resources

„System operacyjny rygorystycznie śledzi dostępne zasoby , a także kontroluje współdzielony dostęp do nich z różnych programów. Na przykład, jeśli jeden program zmieni plik, inny program nie może zmienić (ani usunąć) tego pliku. Ta zasada nie jest ograniczają się do plików, ale stanowią najbardziej zrozumiały przykład.

„System operacyjny ma funkcje (API), które umożliwiają programowi pozyskiwanie i/lub zwalnianie zasobów. Jeśli zasób jest zajęty, tylko program, który go uzyskał, może z nim pracować. Jeśli zasób jest wolny, każdy program może uzyskać To.

„Wyobraź sobie, że biuro ma wspólne kubki do kawy. Jeśli ktoś bierze kubek, inni ludzie już go nie mogą wziąć. Ale kiedy kubek zostanie użyty, umyty i odłożony na swoje miejsce, każdy może go ponownie wziąć”.

„Rozumiem. To tak jak z miejscami w metrze lub innym transporcie publicznym. Jeśli miejsce jest wolne, każdy może je zająć. Jeśli miejsce jest zajęte, kontroluje je osoba, która je zajęła”.

„Zgadza się. A teraz porozmawiajmy o pozyskiwaniu zasobów zewnętrznych . Za każdym razem, gdy twój program Java zaczyna pracować z plikiem na dysku, maszyna Java prosi system operacyjny o wyłączny dostęp do niego. Jeśli zasób jest wolny, maszyna Java uzyskuje To.

„Ale po zakończeniu pracy z plikiem ten zasób (plik) musi zostać zwolniony, tj. musisz powiadomić system operacyjny, że już go nie potrzebujesz. Jeśli tego nie zrobisz, zasób będzie nadal posiadanych przez twój program”.

– To brzmi uczciwie.

„Aby tak było, system operacyjny utrzymuje listę zasobów zajmowanych przez każdy uruchomiony program. Jeśli twój program przekroczy przypisany limit zasobów, system operacyjny nie będzie już dostarczał nowych zasobów.

„To jak programy, które mogą pochłonąć całą pamięć…”

„Coś w tym stylu. Dobrą wiadomością jest to, że jeśli twój program się zakończy, wszystkie zasoby zostaną automatycznie zwolnione (robi to sam system operacyjny).”

„Jeśli to jest dobra wiadomość, czy to znaczy, że jest zła?”

„Dokładnie tak. Zła wiadomość jest taka, że ​​jeśli piszesz aplikację serwerową…”

„Ale czy ja piszę takie podania?”

„Wiele aplikacji serwerowych jest napisanych w Javie, więc najprawdopodobniej będziesz je pisał do pracy. Jak już mówiłem, jeśli piszesz aplikację serwerową, to Twój serwer musi działać non-stop przez dni, tygodnie, miesiące, itp."

„Innymi słowy, program się nie kończy, a to oznacza, że ​​pamięć nie jest automatycznie zwalniana”.

„Dokładnie. A jeśli otworzysz 100 plików dziennie i ich nie zamkniesz, to za kilka tygodni Twoja aplikacja osiągnie limit zasobów i ulegnie awarii”.

„To znacznie mniej niż miesiące stabilnej pracy! Co można zrobić?”

„Klasy korzystające z zasobów zewnętrznych mają specjalną metodę ich uwalniania: close().

„Oto przykład programu, który zapisuje coś do pliku, a następnie zamyka plik po zakończeniu, tj. zwalnia zasoby systemu operacyjnego. Wygląda to mniej więcej tak:

Kod Notatka
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
Ścieżka do pliku.
Pobierz obiekt pliku: zdobądź zasób.
Zapisz do pliku
Zamknij plik - zwolnij zasób

„Ach… Więc po pracy z plikiem (lub innymi zasobami zewnętrznymi) muszę wywołać metodę close()na obiekcie powiązanym z zasobem zewnętrznym”.

„Tak. Wszystko wydaje się proste. Ale podczas działania programu mogą wystąpić wyjątki, a zasoby zewnętrzne nie zostaną zwolnione”.

„I to jest bardzo złe. Co robić?”

„Aby upewnić się, że close()metoda jest zawsze wywoływana, musimy owinąć nasz kod w blok try--- i dodać metodę do bloku. Będzie to wyglądać mniej więcej tak:catchfinallyclose()finally

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

"Hm... Coś tu nie tak?"

„Racja. Ten kod się nie skompiluje, ponieważ outputzmienna jest zadeklarowana wewnątrz try{}bloku, a zatem nie jest widoczna w finallybloku.

Naprawmy to:

FileOutputStream output = new FileOutputStream(path);

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

— Czy teraz wszystko w porządku?

„W porządku, ale nie zadziała, jeśli podczas tworzenia obiektu wystąpi błąd FileOutputStream, a to może się zdarzyć dość łatwo.

Naprawmy to:

FileOutputStream output = null;

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

— I czy wszystko już działa?

„Jest jeszcze kilka uwag krytycznych. Po pierwsze, jeśli podczas tworzenia obiektu wystąpi błąd FileOutputStream, zmienna outputbędzie miała wartość NULL. Taka możliwość musi być uwzględniona w finallybloku.

„Po drugie, close()metoda jest zawsze wywoływana w finallybloku, co oznacza, że ​​nie jest konieczna w trybloku. Ostateczny kod będzie wyglądał następująco:

FileOutputStream output = null;

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

„Nawet jeśli nie weźmiemy pod uwagę catchbloku, który można pominąć, nasze 3 wiersze kodu staną się 10. Ale w zasadzie po prostu otworzyliśmy plik i napisaliśmy 1”.

"Uff... Dobrze, że sprawa jest zakończona. Stosunkowo zrozumiałe, ale trochę nużące, prawda?"

„Tak jest. Dlatego twórcy Javy pomogli nam, dodając trochę cukru składniowego. Przejdźmy teraz do najważniejszego punktu programu, a raczej do tej lekcji:

try-z-zasobami

„Począwszy od 7. wersji, Java ma nową tryinstrukcję -with-resources.

„Został stworzony właśnie po to, aby rozwiązać problem z obowiązkowym wywołaniem metody close()”.

"Brzmi obiecująco!"

„Ogólny przypadek wygląda dość prosto:

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

– Więc to jest kolejna odmiana tego try stwierdzenia ?

„Tak. Musisz dodać nawiasy po słowie trykluczowym, a następnie utworzyć obiekty z zasobami zewnętrznymi w nawiasach. Dla każdego obiektu w nawiasach kompilator dodaje sekcję finallyi wywołanie metody close().

„Poniżej znajdują się dwa równoważne przykłady:

Długi kod Kod z 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);
}

„Fajnie! Kod wykorzystujący opcję try-with-resources jest znacznie krótszy i łatwiejszy do odczytania. A im mniej mamy kodu, tym mniejsze prawdopodobieństwo popełnienia literówki lub innego błędu”.

„Cieszę się, że ci się podoba. Przy okazji, możemy dodawać catchi finallyblokować do tryinstrukcji -with-resources. Lub nie możesz ich dodawać, jeśli nie są potrzebne.

Kilka zmiennych jednocześnie

„Często możesz spotkać się z sytuacją, w której musisz otworzyć kilka plików jednocześnie. Powiedzmy, że kopiujesz plik, więc potrzebujesz dwóch obiektów: pliku, z którego kopiujesz dane i pliku, do którego kopiujesz dane .

„W tym przypadku tryinstrukcja -with-resources pozwala na utworzenie w niej jednego, ale kilku obiektów. Kod, który tworzy obiekty, musi być oddzielony średnikami. Oto ogólny wygląd tej instrukcji:

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

Przykład kopiowania plików:

Krótki kod Długi kod
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();
}

"Cóż, co możemy tu powiedzieć? try-z-zasobami to cudowna rzecz!"

„Możemy powiedzieć, że powinniśmy to wykorzystać”.