„Ostateczne” słowo kluczowe w Javie

1. Przegląd

Dziedziczenie umożliwia nam ponowne wykorzystanie istniejącego kodu, ale czasami z różnych powodów musimy ustawić ograniczenia dotyczące rozszerzalności ; końcowy słów kluczowych pozwala nam dokładnie to zrobić.

W tym samouczku przyjrzymy się, co oznacza końcowe słowo kluczowe dla klas, metod i zmiennych.

2. Zajęcia końcowe

Zajęcia oznaczone jako końcowe nie mogą zostać przedłużone. Jeśli spojrzymy na kod podstawowych bibliotek Javy, znajdziemy tam wiele klas końcowych . Jednym z przykładów jest klasa String .

Rozważmy sytuację, w której możemy rozszerzyć klasę String , przesłonić którąkolwiek z jej metod i zastąpić wszystkie instancje String instancjami naszej określonej podklasy String .

Rezultat operacji na obiektach String stanie się wtedy nieprzewidywalny. A biorąc pod uwagę, że klasa String jest używana wszędzie, jest to niedopuszczalne. Dlatego klasa String jest oznaczona jako ostateczna .

Każda próba dziedziczenia z ostatniej klasy spowoduje błąd kompilatora. Aby to zademonstrować, stwórzmy ostatnią klasę Cat :

public final class Cat { private int weight; // standard getter and setter }

Spróbujmy to przedłużyć:

public class BlackCat extends Cat { }

Zobaczymy błąd kompilatora:

The type BlackCat cannot subclass the final class Cat

Zauważ, że końcowe słowa kluczowego w deklaracji klasy nie oznacza, że obiekty tej klasy są niezmienne . Pola obiektu Cat możemy dowolnie zmieniać :

Cat cat = new Cat(); cat.setWeight(1); assertEquals(1, cat.getWeight()); 

Po prostu nie możemy go przedłużyć.

Jeśli ściśle przestrzegamy zasad dobrego projektowania, powinniśmy starannie stworzyć i udokumentować klasę lub uznać ją za ostateczną ze względów bezpieczeństwa. Jednak przy tworzeniu klas końcowych należy zachować ostrożność .

Wskazówka że uczynienie klasa końcowych oznacza, że żaden inny programista może go poprawić. Wyobraź sobie, że używamy klasy i nie mamy do niej kodu źródłowego i występuje problem z jedną metodą.

Jeśli klasa jest ostateczna, nie możemy jej rozszerzyć, aby zastąpić metodę i rozwiązać problem. Innymi słowy, tracimy rozszerzalność, jedną z zalet programowania obiektowego.

3. Metody końcowe

Metody oznaczone jako ostateczne nie mogą zostać zastąpione. Kiedy projektujemy klasę i uważamy, że metoda nie powinna być nadpisywana, możemy uczynić tę metodę ostateczną . Wiele finalnych metod możemy również znaleźć w podstawowych bibliotekach Java.

Czasami nie musimy całkowicie zabraniać rozszerzenia klasy, a jedynie zapobiegać zastępowaniu niektórych metod. Dobrym tego przykładem jest klasa Thread . Dozwolone jest jej rozszerzenie, a tym samym utworzenie niestandardowej klasy wątku. Ale jego metody isAlive ()ostateczne .

Ta metoda sprawdza, czy wątek żyje. Z wielu powodów niemożliwe jest poprawne przesłonięcie metody isAlive () . Jednym z nich jest to, że ta metoda jest rodzima. Kod natywny jest zaimplementowany w innym języku programowania i często jest specyficzny dla systemu operacyjnego i sprzętu, na którym działa.

Stwórzmy Dog klasę i uczynić jej dźwięk () metoda końcowy :

public class Dog { public final void sound() { // ... } }

Teraz rozszerzmy klasę Dog i spróbujmy przesłonić jej metodę sound () :

public class BlackDog extends Dog { public void sound() { } }

Zobaczymy błąd kompilatora:

- overrides com.baeldung.finalkeyword.Dog.sound - Cannot override the final method from Dog sound() method is final and can’t be overridden

Jeśli niektóre metody naszej klasy są wywoływane innymi metodami, powinniśmy rozważyć nadanie wywoływanych metod ostatecznych . W przeciwnym razie ich zastąpienie może wpłynąć na pracę dzwoniących i spowodować zaskakujące rezultaty.

Jeśli nasz konstruktor wywołuje inne metody, powinniśmy ogólnie zadeklarować te metody jako ostateczne z powyższego powodu.

Jaka jest różnica między ostatecznym uznaniem wszystkich metod klasy a oznaczeniem samej klasy jako ostateczną ? W pierwszym przypadku możemy rozszerzyć klasę i dodać do niej nowe metody.

W drugim przypadku nie możemy tego zrobić.

4. Zmienne końcowe

Zmiennych oznaczonych jako ostateczne nie można ponownie przypisać. Po zainicjowaniu ostatniej zmiennej nie można jej zmienić.

4.1. Ostateczne zmienne pierwotne

Zadeklarujmy pierwotną zmienną końcową i, a następnie przypiszmy do niej 1.

I spróbujmy przypisać mu wartość 2:

public void whenFinalVariableAssign_thenOnlyOnce() { final int i = 1; //... i=2; }

Kompilator mówi:

The final local variable i may already have been assigned

4.2. Ostateczne zmienne odniesienia

Jeśli mamy ostateczną zmienną odniesienia, nie możemy jej również ponownie przypisać. Ale to nie znaczy, że przedmiot, do którego się odnosi, jest niezmienny . Możemy dowolnie zmieniać właściwości tego obiektu.

Aby to zademonstrować, zadeklarujmy ostateczną zmienną referencyjną cat i zainicjujmy ją:

final Cat cat = new Cat();

Jeśli spróbujemy go ponownie przypisać, zobaczymy błąd kompilatora:

The final local variable cat cannot be assigned. It must be blank and not using a compound assignment

Ale możemy zmienić właściwości instancji Cat :

cat.setWeight(5); assertEquals(5, cat.getWeight());

4.3. Ostatnie pola

Pola końcowe mogą być stałymi lub polami jednorazowego zapisu. Aby je rozróżnić, należałoby zadać pytanie - czy uwzględnilibyśmy to pole, gdybyśmy mieli serializować obiekt? Jeśli nie, to nie jest częścią obiektu, ale stałą.

Zwróć uwagę, że zgodnie z konwencją nazewnictwa, stałe klas powinny być pisane wielkimi literami, a komponenty oddzielone znakami podkreślenia („_”):

static final int MAX_WIDTH = 999;

Należy pamiętać, że każde końcowe pole musi zostać zainicjowane przed zakończeniem działania konstruktora .

W przypadku statycznych pól końcowych oznacza to, że możemy je zainicjować:

  • po deklaracji, jak pokazano w powyższym przykładzie
  • w statycznym bloku inicjalizatora

Na przykład pola końcowe , oznacza to, że możemy je zainicjalizować:

  • po zgłoszeniu
  • w bloku inicjalizatora instancji
  • w konstruktorze

W przeciwnym razie kompilator wyświetli błąd.

4.4. Końcowe argumenty

Końcowe słowo kluczowe jest również legalny umieścić przed argumentów metod. Ostateczny argument nie może być zmieniona wewnątrz metody :

public void methodWithFinalArguments(final int x) { x=1; }

Powyższe przypisanie powoduje błąd kompilatora:

The final local variable x cannot be assigned. It must be blank and not using a compound assignment

5. Wniosek

W tym artykule dowiedzieliśmy się, co oznacza końcowe słowo kluczowe dla klas, metod i zmiennych. Chociaż możemy nie używać końcowego słowa kluczowego często w naszym wewnętrznym kodzie, może to być dobre rozwiązanie projektowe.

Jak zawsze pełny kod tego artykułu można znaleźć w projekcie GitHub.