1. Wprowadzenie
W Javie modyfikatory dostępu — to jak system zamków w domu. Określają, kto i skąd może „wejść” do ciebie do pokoju (albo do pola/metody twojej klasy). Jeśli wszystkie drzwi są otwarte — każdy może przyjść i coś zmienić. Jeśli wszystko jest zamknięte — nikt niczego nie zepsuje, ale i ty sam w pewnym momencie możesz zostać zamknięty na klucz.
Przypomnijmy: w Javie są cztery podstawowe poziomy dostępu:
| Modyfikator | Dostępny wewnątrz klasy | Dostępny w pakiecie | Dostępny w podklasach | Dostępny w innych pakietach |
|---|---|---|---|---|
|
✔ | |||
| (package) | ✔ | ✔ | ||
|
✔ | ✔ | ✔ | (przez dziedziczenie) |
|
✔ | ✔ | ✔ | ✔ |
(package) — to wtedy, gdy modyfikator nie jest podany jawnie. Taki członek klasy jest widoczny tylko w obrębie jednego pakietu.
2. Typowe błędy związane z modyfikatorami dostępu
Błąd 1: Pola i metody z domyślnym modyfikatorem (package-private)
Najczęstszy błąd początkujących — zapomnieć wskazać modyfikator dostępu. W rezultacie pole lub metoda stają się dostępne w całym pakiecie, choć nie było to planowane. To może doprowadzić do tego, że inna klasa (w tym samym pakiecie, ale niezwiązana z twoją) będzie mogła zmienić wewnętrzny stan twojego obiektu.
// Błąd: pole name nie jest chronione!
class User {
String name; // package-private!
}
W rezultacie dowolna klasa z tego pakietu może napisać:
User user = new User();
user.name = "John"; // bez ograniczeń!
Błąd 2: Naruszenie enkapsulacji — publiczne pola (public)
Drugi co do popularności błąd — deklarowanie pól klasy jako public. To wygodne, gdy dopiero się uczysz lub piszesz krótki przykład, ale w prawdziwych projektach to niemal zawsze kiepski pomysł. Tracisz kontrolę nad tym, kto i jak zmienia twoje dane.
public class Account {
public double balance; // NIEBEZPIECZNE!
}
Teraz dowolny kod może zrobić:
Account acc = new Account();
acc.balance = -1000000; // I kto jest teraz winny?
Błąd 3: Brak getterów i setterów
Czasem programista robi pola private, ale zapomina dodać metody do zarządzania nimi. W rezultacie nie da się pobrać ani zmienić wartości nawet tam, gdzie byłoby to zasadne.
public class Product {
private String name;
// Brak ani getName(), ani setName()
}
Błąd 4: Próba dostępu do elementów private z innej klasy
Jeśli zadeklarowałeś pole lub metodę jako private, to nie można się do nich odwołać z innej klasy, nawet jeśli znajduje się w tym samym pakiecie. Początkujących często dziwi, dlaczego „nie widzi” pola.
public class User {
private String password;
}
public class UserService {
public void resetPassword(User user) {
// user.password = "123"; // Błąd kompilacji!
}
}
Błąd 5: Błędy związane z protected
Wielu uważa, że protected oznacza „widoczne wszędzie, gdzie jest dziedziczenie”. Ale w Javie dostęp do elementów protected poza pakietem jest możliwy tylko przez dziedziczenie i wyłącznie dla podklasy. To subtelność, którą łatwo przeoczyć.
package animals;
public class Animal {
protected void sleep() {}
}
package zoo;
import animals.Animal;
public class Dog extends Animal {
public void test() {
sleep(); // OK — podklasa
}
}
public class NotADog {
public void test() {
Animal a = new Animal();
// a.sleep(); // Błąd: to nie podklasa!
}
}
3. Jak robić to poprawnie: dobre praktyki
Reguła 1: Domyślnie ustawiaj pola jako private
To główna zasada enkapsulacji. Pola powinny być ukryte przed wszystkimi poza samą klasą. Jeśli trzeba dać dostęp — używaj getterów/setterów.
public class Book {
private String title;
private int pages;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
Reguła 2: Ujawniaj tylko niezbędne metody
Jeśli metoda ma być dostępna z zewnątrz — oznacz ją jako public. Jeśli jest potrzebna tylko w obrębie pakietu — pozostaw package-private. Jeśli metoda jest przeznaczona tylko dla dziedziczących — użyj protected.
Reguła 3: Minimalizuj zakres widoczności
Im mniejszy zakres widoczności, tym mniejsze ryzyko przypadkowych błędów i „nieoczekiwanych gości”. Nie rób metod i pól public, jeśli nie ma takiej potrzeby.
Reguła 4: Używaj getterów i setterów do kontroli dostępu
To pozwala dodać dodatkową logikę podczas odczytu/zapisu pola, np. walidację.
public class Account {
private double balance;
public void setBalance(double balance) {
if (balance < 0) {
throw new IllegalArgumentException("Saldo nie może być ujemne!");
}
this.balance = balance;
}
public double getBalance() {
return balance;
}
}
Reguła 5: Nie ujawniaj wewnętrznej implementacji
Jeśli masz tablicę lub listę jako pole, nie zwracaj jej bezpośrednio przez getter — zwracaj kopię albo udostępniaj tylko potrzebne metody.
public class Team {
private List<String> members = new ArrayList<>();
// Poprawnie:
public List<String> getMembers() {
return new ArrayList<>(members); // zwracamy kopię
}
}
4. Przykłady w praktyce
Załóżmy, że mamy klasę LibraryUser, która opisuje użytkownika biblioteki.
Przykład niepoprawnej implementacji
public class LibraryUser {
public String name;
public int borrowedBooks;
}
W takiej postaci dowolny kod może zrobić z obiektem cokolwiek:
LibraryUser user = new LibraryUser();
user.name = null;
user.borrowedBooks = -10; // Logika? Jaka logika?
Przykład poprawnej implementacji z enkapsulacją
public class LibraryUser {
private String name;
private int borrowedBooks;
public LibraryUser(String name) {
this.name = name;
this.borrowedBooks = 0;
}
public String getName() {
return name;
}
public int getBorrowedBooks() {
return borrowedBooks;
}
public void borrowBook() {
borrowedBooks++;
}
public void returnBook() {
if (borrowedBooks > 0) {
borrowedBooks--;
}
}
}
Teraz kod zewnętrzny nie może bezpośrednio zmienić liczby wypożyczonych książek ani imienia użytkownika. Wszystko jest kontrolowane wyłącznie przez metody klasy.
5. Szczegóły i niuanse implementacji
Czasem wydaje się, że łatwiej zrobić pole public, niż pisać mnóstwo getterów i setterów. Ale to pułapka! Otwarte pole — to jak otwarte drzwi do mieszkania: owszem, wygodnie, ale mało bezpiecznie.
Jeszcze jedna subtelność — nie zawsze trzeba tworzyć gettery i settery dla wszystkich pól. Jeśli wartość pola nie powinna się zmieniać po utworzeniu obiektu, zrób tylko getter, a pole oznacz jako final:
public class Passport {
private final String number;
public Passport(String number) {
this.number = number;
}
public String getNumber() {
return number;
}
}
Pamiętaj także: jeśli klasa jest zadeklarowana jako public, nazwa pliku musi odpowiadać nazwie klasy! To nie do końca o modyfikatorach dostępu, ale to bardzo częsty błąd początkujących.
6. Typowe błędy podczas pracy z modyfikatorami dostępu
Błąd nr 1: zapomniano wskazać modyfikator dostępu przy polu lub metodzie. W rezultacie pole lub metoda stają się dostępne w całym pakiecie, nawet jeśli nie było to zamierzone. Zawsze jawnie określaj modyfikator, nawet jeśli IDE nie narzeka.
Błąd nr 2: wszystkie pola zadeklarowane jako public. To zabija enkapsulację, czyni twój kod podatnym i nieprzewidywalnym. Nawyk z przykładów „dla prostoty” nie powinien trafiać do kodu produkcyjnego.
Błąd nr 3: próba odwołania się do pola private z innej klasy. Java na to nie pozwoli — kompilator cię ochroni, ale jeśli zechcesz to „obejść” refleksją — zastanów się, dlaczego w ogóle pojawiła się taka potrzeba.
Błąd nr 4: oczekiwanie, że elementy protected będą dostępne wszędzie, gdzie jest dziedziczenie. W rzeczywistości poza pakietem można się do nich odwołać tylko z podklasy i tylko przez this lub przez obiekt podklasy.
Błąd nr 5: zwracanie wewnętrznej kolekcji przez getter. Jeśli zwrócisz referencję do wewnętrznej tablicy lub listy, kod zewnętrzny będzie mógł ją zmodyfikować, naruszając inwarianty klasy.
Błąd nr 6: brak kontroli przy ustawianiu wartości przez setter. Jeśli nie zweryfikujesz wartości wejściowej, możesz uzyskać niepoprawny stan obiektu (np. ujemne saldo).
Błąd nr 7: zbyt szeroki zakres widoczności metod. Czasem metody oznacza się jako public, choć są potrzebne tylko w obrębie pakietu lub klasy. To otwiera zbędne API i utrudnia utrzymanie.
GO TO FULL VERSION