1. Zasoby zewnętrzne

Czasami program Java wchodzi w interakcję z obiektami poza maszyną Java podczas jego działania. Na przykład z plikami na dysku. Takie obiekty nazywane są zasobami zewnętrznymi. Zasoby wewnętrzne to obiekty tworzone wewnątrz maszyny Java.

Interakcja zwykle wygląda tak:

spróbuj z oświadczeniem o zasobach

Rachunkowość zasobów

System operacyjny prowadzi ścisły rejestr dostępnych zasobów, a także kontroluje współdzielenie dostępu między różnymi programami do nich. Na przykład, jeśli jeden program zmieni plik, inny program nie może zmienić (ani usunąć) tego pliku. Dotyczy to nie tylko plików, ale ich przykład jest jak najbardziej wyraźny.

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 przechwycił, może z nim pracować. Jeśli zasób jest wolny, każdy program może go pobrać.

Wyobraź sobie, że masz w biurze zwykłe kubki. Jeśli ktoś wziął kubek, drugi nie może już go wziąć. Ale jeśli był używany, wyprany i odłożony na swoje miejsce, każdy może go ponownie wziąć. Cóż, albo miejsca w metrze lub w minibusie. Jeśli miejsce jest wolne, każdy może je zająć. Jeśli miejsce jest zajęte, rozporządza nim ten, kto je zajmował.

Zajęcie zasobów zewnętrznych

Za każdym razem, gdy twój program Java rozpoczyna pracę nad plikiem na dysku, maszyna Java prosi system operacyjny o wyłączny dostęp do tego pliku. Jeśli zasób jest wolny, maszyna Java go przejmuje.

Ale po zakończeniu pracy z plikiem ten zasób (plik) musi zostać zwolniony: powiadom system operacyjny, że już go nie potrzebujesz. Jeśli tego nie zrobisz, zasób będzie nadal przypisany do Twojego programu.

Dla każdego uruchomionego programu system operacyjny przechowuje listę używanych zasobów. Jeśli twój program przekroczy dozwolony limit zasobów, system operacyjny nie będzie już dostarczał nowych zasobów.

Dobrą wiadomością jest to, że jeśli twój program zakończy działanie, wszystkie zasoby zostaną automatycznie zwolnione (robi to sam system operacyjny).

Zła wiadomość jest taka, że ​​jeśli piszesz aplikację serwerową (a wiele aplikacji serwerowych jest napisanych w Javie), Twój serwer musi działać przez wiele dni, tygodni, miesięcy bez zatrzymywania się. A jeśli otworzysz 100 plików dziennie i ich nie zamkniesz, za kilka tygodni Twoja aplikacja osiągnie swój limit i ulegnie awarii. Nie brzmi to jak miesiące stałej pracy.


2. Metodaclose()

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

Poniżej znajduje się przykład programu, który zapisuje coś do pliku i zamyka go za sobą - zwalniając 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.
Otrzymujemy obiekt pliku: pobierz zasób.
Piszemy do pliku
Zamknij plik - zwolnij zasób

Po pracy z plikiem (lub innym zasobem zewnętrznym) należy wywołać metodę close().

Wyjątki

Wszystko wydaje się proste. Jednak podczas wykonywania programu mogą wystąpić wyjątki, a zasób zewnętrzny nigdy nie zostanie zwolniony. I to jest bardzo złe.

Aby upewnić się, że metoda close()jest zawsze wywoływana, musimy otoczyć nasz kod blokiem trycatchfinallyi dodać metodę close()do bloku finally. Będzie to wyglądać mniej więcej tak:

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

Ten kod nie skompiluje się, ponieważ zmienna outputjest zadeklarowana wewnątrz bloku try {}, co oznacza, że ​​nie jest widoczna w bloku finally.

Naprawimy:

FileOutputStream output = new FileOutputStream(path);

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

Ok, ale to nie zadziała, jeśli podczas tworzenia obiektu wystąpił błąd FileOutputStream, co może się zdarzyć bardzo łatwo.

Naprawimy:

FileOutputStream output = null;

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

Jest jeszcze kilka rzeczy do odnotowania. Po pierwsze, jeśli FileOutputStreampodczas tworzenia obiektu wystąpi błąd, zmienna wyjściowa będzie miała wartość null i ten fakt należy uwzględnić w bloku finally.

Po drugie, metoda jest zawsze close()wywoływana w bloku finally, co oznacza, że ​​nie jest potrzebna w bloku try. 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 policzymy bloku catch, który można pominąć, nasz kod składający się z trzech wierszy zamieni się w 10. Chociaż w rzeczywistości otworzyliśmy tylko plik i zapisaliśmy w nim 1. Trochę kłopotliwe, nie sądzisz?


3. try-z-zasobami

Twórcy Javy i wtedy postanowili dodać nam trochę cukru składniowego. Od wersji 7 Javy ma nowy operator try-with-resources ( trywith resources).

Został stworzony tylko po to, aby rozwiązać problem z obowiązkowym wywołaniem metody close(). Ogólnie wygląda to dość prosto:

try (Класс Nazwa = new Класс())
{
     Код, который работает с переменной Nazwa
}

To inny rodzaj operatora try . Po słowie kluczowym trymusisz dodać nawiasy, a wewnątrz nich utworzyć obiekty z zasobami zewnętrznymi. Dla obiektu określonego w nawiasach kompilator sam doda 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);
}

Kod używający tryopcji -with-resources jest znacznie krótszy i łatwiejszy do odczytania. A im mniej kodu, tym mniejsze prawdopodobieństwo popełnienia literówki lub błędu.

Nawiasem mówiąc, operator -with-resources trymoże dodawać bloki catchi pliki finally. I nie możesz dodać, jeśli nie są konieczne.



4. Wiele zmiennych jednocześnie

Nawiasem mówiąc, często może wystąpić sytuacja, gdy trzeba otworzyć kilka plików jednocześnie. Załóżmy, że kopiujesz plik i potrzebujesz dwóch obiektów: pliku, z którego kopiujesz dane, i pliku, do którego kopiujesz dane.

W tym przypadku tryoperator -with-resources pozwala na utworzenie w nim nie jednego obiektu, ale kilku. Kod tworzenia obiektu musi być oddzielony średnikiem. Ogólny widok takiej komendy:

try (Класс Nazwa = new Класс(); Класс2 Nazwa2 = new Класс2())
{
   Код, который работает с переменной Nazwa и Nazwa2
}

Przykład kopiowania plików:

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

Ogólnie, co mogę powiedzieć: to świetna rzecz - tryz zasobami


5. InterfejsAutoCloseable

Ale to nie wszystko. Uważny czytelnik od razu zacznie szukać haczyka w granicy stosowalności tego operatora.

A jak będzie działał operator -with-resources, tryjeśli klasa nie ma metody close()? Cóż, powiedzmy, że nic nie zostanie nazwane. Nie ma metody, nie ma problemu.

A jak będzie działać funkcja -with-resources, tryjeśli klasa ma kilka metod close()? I muszą przekazać parametry? A klasa nie ma metody close()bez parametrów?

Mam nadzieję, że naprawdę zadałeś sobie te pytania i być może nie tylko.

Aby uniknąć takich pytań, twórcy Javy wymyślili specjalną klasę (interfejs), AutoCloseablektóra ma tylko jedną metodę - close()bez parametrów.

Dodaliśmy również ograniczenie, że tylko obiekty klas dziedziczą potryAutoCloseable . Zatem te obiekty zawsze będą miały metodę close()bez parametrów.

Nawiasem mówiąc, co myślisz, czy można przekazać jako zasób do try-with-resources obiekt, którego klasa ma metodę close()bez parametrów, ale z której nie jest dziedziczona AutoCloseable?

Złą wiadomością jest to, że poprawna odpowiedź brzmi nie, klasy muszą implementować AutoCloseable.

Dobrą wiadomością jest to, że istnieje wiele klas w Javie, które implementują ten interfejs, więc jest bardzo prawdopodobne, że wszystko będzie działać tak, jak powinno.