CodeGym /Kursy /JAVA 25 SELF /Modyfikatory dostępu

Modyfikatory dostępu

JAVA 25 SELF
Poziom 15 , Lekcja 1
Dostępny

1. Przegląd modyfikatorów dostępu

W Java są cztery poziomy dostępu dla klas, pól, metod i konstruktorów:

Modyfikator Gdzie dostępny?
public
Wszędzie: wewnątrz klasy, w innych klasach, w innych pakietach
protected
Wewnątrz klasy, w podklasach (dziedziczących), w innych klasach tego samego pakietu
(package-private)
Tylko w obrębie pakietu (gdy modyfikator nie jest jawnie podany)
private
Tylko wewnątrz bieżącej klasy

Przyjrzyjmy się każdemu z nich bliżej — z przykładami, żartami i niespodziewanymi zwrotami akcji.

public — dostęp publiczny

public — to jak ogłoszenie na cały świat: „Wstęp dla wszystkich!”. Jeśli klasa, pole, metoda lub konstruktor jest zadeklarowany jako public, można odwołać się do niego z dowolnej innej klasy, nawet z innego pakietu.

Przykład:

public class Cat {
    public String name;
    public void sayMeow() {
        System.out.println("Miau!");
    }
}

Do tej klasy i jej pól/metod można odwołać się skądkolwiek. To wygodne, jeśli chcesz, aby twoja klasa była dostępna dla wszystkich, na przykład gdy piszesz bibliotekę.

Ale! Publiczne pola — to nie zawsze dobry pomysł (zob. poprzednią lekcję). Zwykle publiczne są tylko metody, które mają być dostępne z zewnątrz, a pola prawie zawsze pozostają private.

private — dostęp tylko wewnątrz klasy

private — to jak sejf z zamkiem szyfrowym: nikt poza samą klasą nie ma dostępu do tych elementów. Nawet klasy dziedziczące (podklasy) nie widzą prywatnych pól i metod!

Przykład:

public class Cat {
    private String secretName;

    public void setSecretName(String name) {
        secretName = name;
    }

    public String getSecretName() {
        return secretName;
    }
}

Tutaj secretName nie można odczytać ani zmienić bezpośrednio z innej klasy. Tylko sam Cat może to zrobić (lub jego metody). To podstawa enkapsulacji: ukrywamy szczegóły wewnętrzne i udostępniamy do nich dostęp tylko przez metody.

protected — dostęp chroniony

protected — to jak przepustka VIP: dostęp mają klasa, jej klasy dziedziczące (nawet jeśli są w innych pakietach) oraz wszystkie klasy w obrębie bieżącego pakietu.

Przykład:

public class Animal {
    protected int age;

    protected void growOlder() {
        age++;
    }
}

Teraz każda klasa dziedzicząca po Animal będzie mogła odwoływać się do pola age i metody growOlder().

public class Cat extends Animal {
    public void haveBirthday() {
        growOlder();
        System.out.println("Kot skończył " + age + " lat!");
    }
}

Dostęp do elementów protected mają też wszystkie klasy w tym samym pakiecie.

(package-private) — dostęp w obrębie pakietu

Jeśli w ogóle nie podano modyfikatora dostępu, element klasy jest traktowany jako package-private (czyli „dostęp domyślny”). To jak drzwi bez zamka, ale tylko dla swoich: dostęp mają wyłącznie klasy z tego samego pakietu.

Przykład:

class Dog {
    String name; // package-private
    void bark() { // package-private
        System.out.println("Hau!");
    }
}

Klasa Dog, jej pole name i metoda bark() są dostępne tylko w obrębie tego samego pakietu. Próba odwołania się do nich z innego pakietu spowoduje błąd kompilacji.

2. Zastosowanie modyfikatorów do pól i metod

Dlaczego pola prawie zawsze są private

Pola klasy to jej stan wewnętrzny. Jeśli pozostawisz je publiczne, dowolny kod zewnętrzny będzie mógł je zmieniać w każdej chwili. To tak, jakbyś pozwolił nieznanym dzieciom bawić się twoimi meblami jak klockami Lego: pewnego dnia obudzisz się, a lodówka stoi do góry nogami w łazience.

Przykład złej enkapsulacji:

public class Person {
    public String name;
    public int age;
}

Przykład dobrej enkapsulacji:

public class Person {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Metody (gettery i settery) pozwalają kontrolować, w jaki sposób klasy zewnętrzne mogą zmieniać pola. Na przykład można zabronić ujemnego wieku.

Kiedy metody są public, protected lub package-private

  • public — gdy metoda ma być dostępna dla wszystkich. Zwykle to podstawowa funkcjonalność klasy.
  • protected — gdy metoda jest potrzebna tylko klasom dziedziczącym lub w obrębie pakietu (np. metody pomocnicze, które mogą być użyteczne w klasach potomnych).
  • package-private — gdy metoda jest potrzebna tylko wewnątrz pakietu, ale nie powinna być dostępna z zewnątrz (np. szczegóły implementacji).
  • private — gdy metoda jest używana wyłącznie wewnątrz samej klasy (np. metody pomocnicze dla logiki wewnętrznej).

Przykład:

public class BankAccount {
    private double balance;

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    protected void applyInterest() {
        balance *= 1.05;
    }

    void internalAudit() {
        // package-private: tylko dla klas w obrębie pakietu
    }

    private void logAction(String action) {
        // tylko na potrzeby wewnętrzne klasy
    }
}

3. Przykłady kodu: klasa z różnymi poziomami dostępu

Stwórzmy klasę, w której jest wszystko: publiczne, prywatne, chronione i pakietowe elementy. Przy okazji spróbujmy odwołać się do nich z innych klas i zobaczmy, co z tego wyniknie.

package zoo;

public class Animal {
    public String publicName = "Dostępne dla wszystkich";
    protected String protectedName = "Tylko dla dziedziczących i pakietu";
    String packageName = "Tylko dla pakietu";
    private String privateName = "Tylko dla Animal";

    public void publicMethod() {
        System.out.println("Metoda publiczna");
    }

    protected void protectedMethod() {
        System.out.println("Metoda chroniona");
    }

    void packageMethod() {
        System.out.println("Metoda pakietowa");
    }

    private void privateMethod() {
        System.out.println("Metoda prywatna");
    }
}

Teraz spróbujmy odwołać się do tych elementów z innej klasy w tym samym pakiecie:

package zoo;

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.publicName);      // OK
        System.out.println(animal.protectedName);   // OK
        System.out.println(animal.packageName);     // OK
        System.out.println(animal.privateName);  // Błąd: private
        animal.publicMethod();                      // OK
        animal.protectedMethod();                   // OK
        animal.packageMethod();                     // OK
        animal.privateMethod();                  // Błąd: private
    }
}

A teraz spróbujmy odwołać się do tych elementów z innego pakietu:

package other;

import zoo.Animal;

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.publicName);      // OK
        System.out.println(animal.protectedName); // Błąd: protected
        System.out.println(animal.packageName);   // Błąd: package-private
        System.out.println(animal.privateName);   // Błąd: private
        animal.publicMethod();                      // OK
        animal.protectedMethod();                // Błąd: protected
        animal.packageMethod();                  // Błąd: package-private
        animal.privateMethod();                  // Błąd: private
    }
}

Wnioski:

  • public — dostępny wszędzie.
  • protected — dostępny w obrębie pakietu i w klasach dziedziczących (nawet z innych pakietów, jeśli przez dziedziczenie).
  • package-private — tylko w obrębie pakietu.
  • private — tylko wewnątrz klasy.

4. Dobre praktyki: jak wybierać modyfikator dostępu

Minimalizuj zakres widoczności

Im mniej kodu może widzieć twoje pole lub metodę, tym lepiej. Udostępniaj tylko to, co naprawdę jest potrzebne kodowi zewnętrznemu. To nazywa się zasadą najmniejszych uprawnień (principle of least privilege).

  • Pola prawie zawsze powinny być private. Wyjątki — tylko dla prawdziwych stałych (public static final), ale o tym więcej w kolejnych wykładach.
  • Metody oznaczamy jako public tylko wtedy, gdy są częścią zewnętrznego interfejsu klasy.
  • Metody pomocnicze (logika wewnętrzna) — private.
  • Metody dla klas dziedziczącychprotected.
  • Wewnętrzne metody serwisowe dla pakietupackage-private.

Dlaczego to ważne?

  • Każda zmiana w szczegółach implementacji może zepsuć cudzy kod, jeśli szczegóły są ujawnione.
  • Klasa staje się trudniejsza w testowaniu i utrzymaniu.
  • Przypadkowe błędy (np. nieprawidłowa zmiana pola) mogą prowadzić do bugów.

Czasem początkujący myślą: „Po co mi te komplikacje, niech wszystko będzie public!” Ale później, gdy projekt się rozrasta, trzeba przepisywać połowę programu tylko dlatego, że ktoś bezpośrednio zmieniał pola klasy.

5. Typowe błędy podczas pracy z modyfikatorami dostępu

Błąd nr 1: Zostawiono pola public lub domyślnie package-private.
Jeśli nie podasz modyfikatora, pole lub metoda będzie dostępna dla wszystkich klas w pakiecie. Może to prowadzić do nieoczekiwanych sytuacji, gdy ktoś zacznie bezpośrednio zmieniać twoje pola.

Błąd nr 2: Próba odwołania się do elementu private z innej klasy.
Kompilator na to nie pozwoli — dostaniesz błąd. A jeśli spróbujesz to obejść przez reflection (refleksja) — witaj w świecie błędów i nieoczekiwanych awarii.

Błąd nr 3: Za dużo public.
Jeśli wszystko jak leci deklarować jako public, klasa staje się podobna do otwartego pudełka z kablami — każdy może pociągnąć nie za ten przewód i wszystko zepsuć.

Błąd nr 4: Nie używasz protected dla metod potrzebnych tylko klasom dziedziczącym.
Jeśli metoda jest potrzebna wyłącznie do rozszerzania w klasach potomnych, zadeklaruj ją jako protected, a nie public.

Błąd nr 5: Niejawna widoczność package-private.
Czasem zapomina się podać modyfikator i metoda staje się dostępna dla całego pakietu. To może być zaskoczeniem, jeśli sądziłeś, że jest private.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION