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ć.
1 2 |
final int f = 2; f = 4; //Error: cannot assign a value to final variable f |
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.
1 2 3 |
final int f; f = 2; //ok f = 4; //Error: variable f might already been assigned |
1 2 3 |
void foo(final int param) { param = 4; //Error: final parameter param may not be assigned } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Animal { final void makeSound() { System.out.println("uuuUUuu"); } } class Dog extends Animal { @Override void makeSound() { //Error: makeSound() in Dog cannot override makeSound() in Animal; overridden method is final System.out.println("woof"); } } |
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ą).
1 2 3 4 5 6 7 |
final class Animal { } class Dog extends Animal { //Error: cannot inherit from final Animal } |
1 2 3 4 5 6 7 8 9 10 |
class Dog { String name; Dog(String name) { this.name = name; } } final Dog dog = new Dog("Fafik"); dog.name = "Reksio"; //ok, wnętrze obiektu można zmieniać dog = new Dog("Idefiks"); //Error: cannot assign a value to final variable dog |
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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
try { int x = 2/0; } catch (ArithmeticException e) { System.out.println("Exception handled"); } finally { System.out.println("Finally executed"); } System.out.println("Rest of code"); /*Output: Finally executed Exception handled Rest of code */ |
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.
- 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
String foo() { try { int x = 2/0; return "try"; } catch (ArithmeticException e) { return "catch"; } finally { return "finally"; } } //returns: "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.
1 2 3 4 5 6 |
class SomeObject { @Override //przesłonienie metody z klasy Object public void finalize() { System.out.println("GC is coming for me!"); } } |
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.