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:
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 |
---|---|
|
Ś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 try
— catch
— finally
i 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 output
jest 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 FileOutputStream
podczas 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 ( try
with 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 try
musisz dodać nawiasy, a wewnątrz nich utworzyć obiekty z zasobami zewnętrznymi. Dla obiektu określonego w nawiasach kompilator sam doda sekcję finally
i wywołanie metody close()
.
Poniżej znajdują się dwa równoważne przykłady:
Długi kod | Kod z try-with-resources |
---|---|
|
|
Kod używający try
opcji -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 try
może dodawać bloki catch
i 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 try
operator -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 |
---|---|
|
|
Ogólnie, co mogę powiedzieć: to świetna rzecz - try
z 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, try
jeś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, try
jeś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), AutoCloseable
która ma tylko jedną metodę - close()
bez parametrów.
Dodaliśmy również ograniczenie, że tylko obiekty klas dziedziczą potry
AutoCloseable
. 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.
GO TO FULL VERSION