Grudzień 20, 2018

Słowo kluczowe final

Słowo kluczowe final

Na początku mojej drogi z programowaniem w Javie wydawało mi się, że słowo final jest proste i oczywiste. Po jakimś czasie okazało się, że ma więcej zastosować niż ja wcześniej myślałem.

W tym artykule zabiorę Cię do swojego labolatorium i pokażę Ci co można zyskać korzystając ze słówka kluczowego final w Javie.

Pole

Pierwszym zagadnieniem jakim się porusza podczas przerabiania final jest  możliwość oznaczenie pola jako stałą.

Nie jest to nic trudnego, wystarczy przed typem zmiennej dodamy final.

final String name = "Pablo";

Często wykorzystuje się final podczas deklarowania pól statycznych np.:

static final String CONFIG_NAME = "config.ini";

Niestety słowo final nie zapewnia nam niezmienności obiektu, czyli możemy bez problemu zmieniać jego zawartość:

final User user = new User("Pablo", "pablo1", "pablo@example.com");
user.setLogin("NewPablo");

Ale za to pilnuje tego, aby nie została zmieniona referencja obiektu – krótko mówiąc, nie możemy zmienić „przypisania” do zmiennej, więc taka operacja nie jest możliwa do wykonania:

final User user = new User("Pablo", "pablo1", "pablo@example.com");
user.setLogin("NewPablo");
  
user = new User("newPablo", "pablo2", "pablo@example.com");

W przypadku, gdy tworzymy klasę, a pole oznaczymy jako final musimy zapewnić wtedy inicjalizację pola od razu podczas deklaracji lub w konstruktorze.

public final class User {
    private final String login;
    private final String password;
    private final String email;

    public User(String login, String password, String email) {
        this.login = login;
        this.password = password;
        this.email = email;
    }

    public String getLogin() {
        return login;
    }

    public String getPassword() {
        return password;
    }

    public String getEmail() {
        return email;
    } 
}

Ponownie otrzymujemy niezmienność pól – krótko mówiąc w takiej sytuacji settery nie zadziałają. Nawet generator setterów ALT + INSERT w IntelliJ podpowiada nam, że nie może znaleźć pól, dla których mógłby wygenerować settera. 😉

Argumenty final

Słowa kluczowe final możemy również korzystać podczas podawania argumentów metody. Oczywiście nie bezpośrednio podczas wywołania funkcji, tylko podczas jej tworzenia.

public static void transformUser(final User user) {
    user = new User("Pablo", "pablo1", "pablo@example.com");
}

Ponownie taki kod nie zostanie skompilowany – zadeklarowanie argumentu jako final pilnuje tego, aby referencja obiektu w środku nie została zmieniona.

Metoda jako finalna

Słówka final możemy również użyc podczas deklaracji metody. Użyjmy jej podczas deklaracji metody addUser.

import java.util.LinkedList;
import java.util.List;
import java.util.Optional;

public class UserService {
    protected final List<User> users = new LinkedList<>();

    public final void addUser(final User user) {
        users.add(user);
    }

    public Optional<User> getUser(User user) {
        return users.stream().filter(u -> u.getEmail().equals(user.getEmail())).findFirst();
    }
}

Stwórzmy klasę SuperUserService, która będzie rozszerać klasę UserService:

public class SuperUserService extends UserService {

 

}

Przysłońmy obie metody w nowej klasie – używając adnotacji @Override 

import org.springframework.util.Assert;

import java.util.Optional;

public class SuperUserService extends UserService {

    @Override
    public Optional<User> getUser(User user) {
        Assert.notNull(user, "User cannot be null!");
        Assert.hasText(user.getEmail(), "Email cannot be empty!");

        return getUser(user);
    }

    @Override
    public void addUser(User user) {
        Assert.notNull(user, "User cannot be null!");

        super.addUser(user);
    }

}

Obie metody zostały lekko rozszerzone poprzez sprawdzanie podanych argumentów – zostało do tego wykorzystane Assert ze Spring Core.

Niestety kod się nie kompiluje – podświetla nam dokładnie w tej linii:

public void addUser(User user)

Dlaczego tutaj? Dzieje się tak, z powodu oznaczenia metody jako finalna w klasie rodzica:

public final void addUser(final User user)

Jak widać oznaczenie metody jako final zapewnia nam to, że nie może ona zostać przysłonięta w klasie podrzędnej. 😉

Klasa jako final

Ostatnią możliwością jest oznaczenie klasy jako final – sprawdźmy teraz co dzięki temu zyskujemy.

public final class User {
    private final String login;
    private final String password;
    private final String email;

    public User(String login, String password, String email) {
        this.login = login;
        this.password = password;
        this.email = email;
    }

    public String getLogin() {
        return login;
    }

    public String getPassword() {
        return password;
    }

    public String getEmail() {
        return email;
    }
}

Klasa User została oznaczona jako final, stwórzmy teraz klasę SuperUser, która będzie rozszerzać klasę User.

public class SuperUser extends User {
    private final String superPower;

    public SuperUser(String login, String password, String email, String superPower) {
        super(login, password, email);
        this.superPower = superPower;
    }

    public String getSuperPower() {
        return superPower;
    }
}

Jak widać ponownie – kod się nie kompiluje.

Dzieje się tak, ponieważ klasa rodzica (User) została oznaczona jako final – jest finalna. Podsumowując oznaczenie klasy jako final uniemożliwia jej dziedziczenie.

Czy taki mechanizm jest przydatny?

W pewnych przypadkach na pewno tak, m.in korzysta z niego klasa String – używa tego, ponieważ jej twórcy z założenia nie chcą, aby klasa była rozszerzana przez innych programistów, aby nie narazać niezmienności obiektu. W końcu String jest używany np. do przechowywania haseł i przeróżnych kluczy. 😉

Podsumowanie

Powyżej przedstawiłem Ci 4 sytuacje kiedy możesz używać słowa kluczowe final. Co prawda prawie żaden kod się nie kompiluje, lecz mam nadzieje, że te czerwone podkreślenia w IDE zapadną Ci w pamięć i zapamiętasz co daje nam użycie słowa kluczowe final. 😉

Kod używany w artykule znajdziesz w tym repozytorium.