Artykuł ten wymaga podstawowej wiedzy na temat języka Java. Z tego powodu, aby móc w pełni z niego skorzystać, warto zapoznać się z wcześniejszymi artykułami:

UWAGA

Artykuł ten nie zawiera informacji na temat modułów. Moduły zostały wprowadzone do języka Java wraz z wersją 9. Moduły, podobnie jak modyfikatory dostępu także mają wpływ na widoczność. Operują one na pakietach. W artykule tym zakładam, że każdy z pakietów jest eksportowany przez moduł, w którym się znajduje.

Więcej na temat modułów przeczytasz w osobnym artykule.

Czym są modyfikatory dostępu

Modyfikatory dostępu to słowa kluczowe, które mają wpływ na widoczność elementu który poprzedzają. Są to słowa kluczowe public, protected i private. Brak jakiegokolwiek ze wspomnianych słów kluczowych także ma wpływ na dostępność danego elementu. Czasami brak modyfikatora dostępu określa się jako dostęp typu “package”. Modyfikatory dostępu mogą być stosowane na przykład przed definicją klasy, czy interfejsu. Możemy ich także używać przed polami klasy, metodami czy typami wewnętrznymi.

Rodzaje modyfikatorów dostępu

Modyfikator public

Słowo kluczowe public jest modyfikatorem dostępu, który pozwala na najbardziej swobodny dostęp do elementu, który poprzedza. public może być używane przed definicjami klas, pól w klasach, metod czy typów wewnętrznych. Zakładając, że klasa poprzedzona jest public i element w tej klasie jest także public, jest on dostępny dla wszystkich1.

Poniższy fragment kodu pokazuje kasę PublicVisitCounter. Klasa ta implementuje licznik odwiedzin. Założenie jest takie, że każdy użytkownik wywoła metodę increment. Dzięki takiej klasie można w łatwy sposób zliczyć liczbę wizyt na stronie:

package pl.samouczekprogramisty.kursjava.accessmodifiers.public_keyword;

public class PublicVisitCounter {
    public int userCount = 0;

    public void increment() {
        userCount++;
    }
}

Klasa dostępna jest dla wszystkich, ze względu na modyfikator public. Zawiera jedno pole userCount, metodę increment i domyślny konstruktor. Każdy z tych elementów ma dostęp typu public. Oznacza to tyle, że jest dostępny dla wszystkich.

Ma to swoje konsekwencje. Wyobraźmy sobie klasę MaliciousUser, która informuje PublicVisitCounter o swojej wizycie na stronie:

package pl.samouczekprogramisty.kursjava.accessmodifiers.public_keyword;

public class MaliciousUser {
    public void countMyVisit(PublicVisitCounter counter) {
        counter.increment();
        counter.userCount = -10;
    }
}

Jak widzisz, dzięki modyfikatorowi public przed polem userCount instancja MaliciousUser ma dostęp do pola userCount. W takim przypadku możemy mówić o tym, że obiekt PublicVisitCounter udostępnia swój stan na zewnątrz. Nie jest to dobrą praktyką.

Modyfikator protected

Modyfikator protected ma znaczenie w przypadku dziedziczenia. Elementy poprzedzone tym modyfikatorem dostępu są udostępnione dla danej klasy i jej podklas. Dodatkowo elementy oznaczone modyfikatorem protected dostępne są dla innych klas w tym samym pakiecie. Modyfikatora protected nie można stosować przed klasami2. Proszę spójrz na przykład poniżej:

package pl.samouczekprogramisty.kursjava.accessmodifiers.protected_keyword;

public class Pen {
    protected String color;

    public Pen(String color) {
        this.color = color;
    }
}
package pl.samouczekprogramisty.kursjava.accessmodifiers.protected_keyword.different_package;

public class BallPen extends Pen {
    protected String manufacturer;

    public BallPen(String color, String manufacturer) {
        super(color);
        this.manufacturer = manufacturer;
    }

    @Override
    public String toString() {
        return manufacturer + " " + color;
    }
}

Klasa Pen posiada pole color, które poprzedzone jest słowem protected. Dzięki temu klasa BallPen ma dostęp do tego pola. Używa go w implementacji metody toString. Proszę zwróć uwagę na to, że obie klasy znajdują się w różnych pakietach. Mimo to słowo kluczowe protected pozwala na dostęp do pola color.

Jak wspomniałem wcześniej ten modyfikator dostępu pozwala także na dostęp dla klas z tego samego pakietu. Ten przypadek pokazuje klasa poniżej:

package pl.samouczekprogramisty.kursjava.accessmodifiers.protected_keyword;

public class PenOwner {
    private Pen pen;

    public PenOwner(Pen pen) {
        this.pen = pen;
    }

    @Override
    public String toString() {
        return "Mam pioro w kolorze " + pen.color;
    }
}

W tym przypadku PenOwner ma dostęp do pola color ponieważ obie klasy znajdują się w tym samym pakiecie pl.samouczekprogramisty.kursjava.accessmodifiers.protected_keyword.

Pobierz opracowania zadań z rozmów kwalifikacyjnych

Przygotowałem rozwiązania kilku zadań z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 1000 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.

Brak modyfikatora dostępu

Brak modyfikatora dostępu również ma znaczenie. W przypadku gdy pominiemy modyfikator dostępu wówczas dana klasa czy element jest dostępna wyłącznie wewnątrz tego samego pakietu. Jest to podzbiór uprawnień, które nadaje modyfikator protected. Proszę spójrz na przykład poniżej:

package pl.samouczekprogramisty.kursjava.accessmodifiers.missing_keyword;

public class Car {
    public static final double FUEL_TANK_CAPACITY = 50.0;

    double fuelLevel = 12.5;
}
package pl.samouczekprogramisty.kursjava.accessmodifiers.missing_keyword;

public class FuelStation {
    public void fillUp(Car car) {
        double toFill = Car.FUEL_TANK_CAPACITY - car.fuelLevel;
        System.out.println("Tankuje " + toFill + " litrow.");
        car.fuelLevel = Car.FUEL_TANK_CAPACITY;
    }
}

Modyfikator private

Słowo kluczowe private jest najbardziej restrykcyjnym modyfikatorem dostępu. Może być stosowane wyłącznie przed elementami klasy, w tym przed klasami wewnętrznymi. Oznacza on tyle, że dany element (klasa, metoda, czy pole) widoczny jest tylko i wyłącznie wewnątrz klasy. Proszę spójrz na zmodyfikowaną klasę licznika:

package pl.samouczekprogramisty.kursjava.accessmodifiers.encapsulated;

public class EncapsulatedVisitCounter {
    private int userCount = 0;

    public void increment() {
        userCount++;
    }

    public int getUserCount() {
        return userCount;
    }
}

W tym przypadku pole userCount poprzedzone jest słowem kluczowym private. Dzięki niemu stan wewnętrzny klasy nie jest dostępny na zewnątrz. Tylko elementy wewnątrz definicji klasy mają dostęp do tego pola.

Porównanie modyfikatorów dostępu

Informacje na temat działania modyfikatorów dostępu można zebrać je w następującej tabeli:

Modyfikator Klasa Pakiet Podklasa Inni Poprawny dla klas
public tak tak tak tak tak
protected tak tak tak nie nie
brak modyfikatora tak tak nie nie tak
private tak nie nie nie nie

Enkapsulacja, czyli kiedy używać modyfikatorów dostępu

Enkapsulacja (ang. encapsulation), czy inaczej hermetyzacja to sposób na ukrycie szczegółów implementacji klasy. Enkapsulacja to bardzo ważny element programowania obiektowego. Pozwala to na pełną kontrolę nad zachowaniem i stanem danego obiektu.

Dobrą praktyką jest stosowanie najbardziej restrykcyjnych modyfikatorów dostępu. Sprowadza się to do użycia private dla wszystkich pól i metod, które powinny być używane “wewnątrz”. Pozostałe elementy, które stanowią interfejs komunikacji oznaczamy słowem kluczowym public. Brak modyfikatora dostępu czy protected mają znaczenie w przypadku bardziej złożonych relacji pomiędzy obiektami.

Fragment kodu poniżej pokazuje licznik, który poprawnie ukrywa swój stan. Pozwala on na modyfikację czy dostęp do userCount wyłącznie poprzez publiczny interfejs - metody increment i getUserCount:

package pl.samouczekprogramisty.kursjava.accessmodifiers.encapsulated;

public class EncapsulatedVisitCounter {
    private int userCount = 0;

    public void increment() {
        userCount++;
    }

    public int getUserCount() {
        return userCount;
    }
}

Dodatkowe informacje

Modyfikatory dostępu a interfejsy i typy wyliczeniowe

Chciałbym Cię uczulić na przypadek interfejsów. Brak modyfikatora dostępu w definicji interfejsu oznacza, że dana metoda ma modyfikator public. Proszę spójrz na przykład poniżej:

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Jest to interfejs Supplier dostępny w standardowej bibliotece języka Java. Jak widzisz przed metodą get nie ma żadnego modyfikatora. W przypadku interfejsów oznacza to, że dana funkcja jest dostępna publicznie.

Innym przykładem są wartości typu wyliczeniowego. Poniższy przykład to typ wyliczeniowy AccessMode ze standardowej biblioteki. Jego wartości READ, WRITE i EXECUTE są dostępne publicznie mimo braku jakiegokolwiek modyfikatora dostępu:

public enum AccessMode {
    READ,
    WRITE,
    EXECUTE;
}

Interfejs Supplier jest generyczny, często jest wykorzystywany wraz z wyrażeniami lambda. Jeśli chcesz przeczytać więcej na ten temat zapraszam do oddzielnych artykułów:

Modyfikatory dostępu a dziedziczenie

Dzięki mechanizmowi nadpisywania metod mamy możliwość nadpisywania modyfikatorów dostępu. Jest to możliwe w przypadku dziedziczenia. Jeśli dziedziczymy po innej klasie mamy możliwość rozszerzenia dostępu do danej metody. W praktyce mamy dwie metody, jedną w klasie bazowej i kolejną w klasie potomnej:

public class Tree {
    protected int height = 12;

    protected void prune() {
        if (height > 15) {
            height -= 1;
        }
    }

    public void grow() {
        height += 1;
    }
}
public class Oak extends Tree {
    @Override
    public void prune() {
        super.prune();
    }
}

Aby uniemożliwić przedefiniowanie metody należy umieścić przed nią słowo kluczowe final.

Modyfikatory dostępu a mechanizm refleksji

Tylko i wyłącznie dla pełnego obrazu napiszę Ci o mechanizmie refleksji. W większości produkcyjnego kodu nie jest on używany. Pozwala on na dostęp do dowolnego elementu klasy pomijając modyfikator dostępu. Proszę spójrz na przykład poniżej:

public class BankAccount {

    private int balance = 100;

    public int getBalance() {
        return balance;
    }
}
public class Thief {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        BankAccount account = new BankAccount();
        System.out.println("Stan konta: " + account.getBalance());

        Field balance = BankAccount.class.getDeclaredField("balance");
        balance.setAccessible(true);
        balance.set(account, -5000);

        System.out.println("Stan konta: " + account.getBalance());
    }
}

Dzięki mechanizmowi refleksji zmieniłem wartość pola prywatnego. Po uruchomieniu takiego programu na konsoli wyświetlą się dwie linijki:

Stan konta: 100
Stan konta: -5000

Ogólna reguła brzmi - nie używaj mechanizmu refleksji w produkcyjnym kodzie. Chyba, że wiesz co robisz i rzeczywiście jest to potrzebne ;).

Zadanie

Napisz program, który będzie symulował działanie banku. Zaimplementuj następujące interfejsy:

public interface Account {
    void deposit(int amount);
    void withdraw(int amount);
}
public interface BankTransfer {
    void transfer(BankAccount from, BankAccount to, int amount);
}

Bank przeprowadzający operację przesyłu środków pobiera stałą opłatę 1zł od nadawcy przelewu. Jakich modyfikatorów dostępu użyjesz? Dlaczego akurat tych?

Pamiętaj, że nie ma jednego rozwiązania tego zadania. Jest ich nieskończenie wiele, jedno z przykładowych rozwiązań znajdziesz na samouczkowym githubie.

Dodatkowe materiały do nauki

Przygotowałem dla Ciebie zestaw kilku linków z materiałami dodatkowymi:

Podsumowanie

Modyfikatory dostępu w Javie są bardzo ważne. Po przeczytaniu artykułu wiesz czym do czego służą i jak ich używać. Wiesz czym jest hermetyzacja i dlaczego jest istotna. Dowiedziałeś się czegoś więcej mechanizmie refleksji i wiesz, że nie powinieneś go używać ;). Po rozwiązaniu zadania przećwiczyłeś wiedzę z artykułu w praktyce.

Mam nadzieję, że artykuł był dla Ciebie pomocny. Jeśli tak to proszę podziel się z nim ze swoimi znajomymi. Jeśli nie chcesz pominąć żadnego artykułu w przyszłości proszę dopisz się do samouczkowego newslettera i polub Samouczka na Facebooku. Do następnego razu!

  1. Jak wspomniałem we wstępie pomijam tutaj moduły, które mogą ograniczyć dostęp do elementów poprzedzonych słowem kluczowym public

  2. Chyba, że są to klasy wewnętrzne. W takim przypadku modyfikator protected jest dozwolony. 

Pobierz opracowania zadań z rozmów kwalifikacyjnych

Przygotowałem rozwiązania kilku zadań z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 1000 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.

Zostaw komentarz