Java HR: final, finally i finalize

Java HR: final, finally i finalize

Trzy słowa kluczowe w Javie, które brzmią łudząco podobnie, a ich znaczenie jest zupełnie inne. Przyjrzyjmy się dzisiaj final, finally oraz finalize.

Final


Słowo kluczowe final oznacza po prostu, że coś nie może zostać zmienione (zmienić swojej wartości). Co to dokładniej oznacza zależne jest od tego, w jakim kontekście go użyjemy – dla zmiennej, metody czy klasy.

1. Zmienna
Najbardziej oczywistym przypadkiem jest użycie final przy zmiennej (dowolnej – lokalnej czy będącej polem klasy). Wtedy zmienna staje się de facto stałą. Po pierwszym zainicjowaniu takiej zmiennej nie możemy już jej wartości zmienić.

Przykład:

Pozwala to na zachowanie większej stabilności programu, jeśli jakaś wartość faktycznie powinna być stałą. W każdym momencie trwania programu mamy pewność, że wartość ta jest prawidłowa.

Pierwszej (i jedynej) inicjalizacji możemy również dokonać już po deklaracji:

W szczególnym przypadku jako final może być określony także parametr metody – wtedy taka wartość pozostaje niezmienna w całej tej metodzie:

2. Metoda
Użycie final na metodzie sprawia, że nie można jej przesłonić w klasie dziedziczącej. Metoda staje się więc niezmienna – jej ciało definiujemy tylko raz i mamy pewność, że metoda ta nie zostanie zmieniona w innym miejscu, mechanizm ten jest więc analogiczny do poprzedniego przykładu ze zmiennymi.

Przykład:

3. Klasa
Finalna może być także klasa, nie może wtedy zostać rozszerzona. Tu także łatwo dopatrzeć się analogii – definiujemy ostatecznie jakiś obiekt i definicja ta nie może zostać zmieniona poprzez jej uszczegółowienie (poprzez klase dziedziczącą).

Przykład:

Jeśli chodzi o obiekty, to samo użycie słowa kluczowego final przy zmiennej przechowującej jego referencję nie sprawi, że właściwości (wnętrza) obiektu nie będzie można zmienić:

W takiej sytuacji finalna staje się tylko zmienna, czyli nie możemy do niej podstawić referencji do innego obiektu.

Można jednak sprawić, żeby obiekt stał się niezmienny (immutable), również wykorzystując do tego final, ale w nieco bardziej złożony sposób – więcej o tym mechanizmie w artykule Java HR: obiekty niezmienne (immutable).

Finally


Finally jest słowem, którym oznacza się blok kodu, który zawsze powinien się wykonać. Używa się go przy obsłudze wyjątków, dlatego występuje zawsze po bloku catch, lub bezpośrednio po try (catch jest opcjonalny).

Przykład:

Jest to szczególnie przydatne do czyszczenia zasobów, tj. przykładowo zamykania streamów czy połączeń do bazy danych. Obecnie finally może być pomijane na rzecz mechanizmu try-with-resources, o czym napiszę w kolejnym artykule.

Kod w bloku finally wykona się w każdej z tych sytuacji:
  • w bloku try nie zostanie rzucony wyjątek
  • w bloku try zostanie rzucony wyjątek, ale nie zostanie obsłużony w catch
  • w bloku try zostanie rzucony wyjątek i zostanie obsłużony w catch
  • w bloku try wystąpi return, więc teoretycznie metoda w tym momencie powinna zostać zakończona – najpierw jednak wykona się finally

Jeśli wyjątek zostanie rzucony i nie zostanie przechwycony, albo wcześniej nastąpi return, to kod po bloku finally nie zostanie już wykonany.

Szczególny przypadek – zarówno w bloku try, jak i finally może wystąpić return. Wtedy ostatecznie wykonuje się kod zawarty w finally:

Zatem czy kod w finally wykona się zawsze? Odpowiedź tutaj: Java HR: Czy kod w bloku finally zawsze się wykona?

Finalize


Uwaga: metoda Object.finalize() została oznaczona w Javie 9 jako deprecated.
Link do dokumentacji (zostało tam przy okazji wyjaśnione czemu używanie finalize() było niewłaściwe):
https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#finalize–

Ostatni z przypadków – finalize. W przeciwieństwie do final i finally, nie jest w Javie słowem kluczowym (możemy w ten sposób nazwać klasę lub metodę). Jest to domyślna metoda w klasie Object o której nieczęsto się słyszy, bo jest stosunkowo rzadko używana – poleganie na niej jest w większości przypadków złą praktyką.
Jest ona wykonywana tylko jeden raz (chyba że wcześniej wywołamy ją w kodzie sami), zaraz przed tym gdy Garbage Collector (GC) chce usunąć ten obiekt z pamięci.

Przykład:

Możemy wykonać tam wszystkie operacje, które mają na celu posprzątanie pewnych zasobów, podobnie jak w przypadku finally – tym razem jednak w skali całego obiektu. Można więc zamknąć tam wszelkie połączenia do bazy danych, sieci, otwarte pliki itp.
Należy to jednak traktować jako dodatkowe zabezpieczenie na pewne wyjątkowe sytuacje – nie mamy bowiem pewności kiedy finalize() się wykona i czy w ogóle się wykona zanim program zakończy swoje działanie. Nie należy więc umieszczać w niej kodu który musi się wykonać – do tego służy właśnie blok finally.

Uwaga: Nie należy traktować metody finalize() jako destruktora! Usuwaniem obiektów z pamięci zajmuje się Garbage Collector, który jest zależny od implementacji JVM i jest poza kontrolą programisty.

Ciekawostka: nieprzechwycone wyjątki wewnątrz metody finalize() są ingorowane i program normalnie wykonuje się dalej (ale tylko jeśli została ona wywołana przez GC, nie ręcznie przez programistę). Nie zostaną one nawet w żaden sposób zalogowane. Przechwycone wyjątki mogą zaś zostać obsłużone.

Informatyk, programista. Obecnie Java Developer (Web Fullstack), właściciel studia Berrygames oraz prezes koła TK Games na Politechnice Wrocławskiej.

Wszystkie artykuły autora>>

Dodaj komentarz