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ń algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 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:
- fragment kursu na stronie Oracle opisujący modyfikatory dostępu,
- artykuł na Wikipedii na temat hermetyzacji,
- przykłady kodu użyte w artykule.
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!
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 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