1. Czym są gettery i settery
Jeśli wyobrazić sobie obiekt jako sejf, to jego prywatne pola — to zawartość sejfu, a gettery i settery — klucze do poszczególnych przegródek. Gettery pozwalają sprawdzić, co jest w środku, a settery — ostrożnie włożyć tam coś nowego (ale tylko jeśli nie wkładasz tam, na przykład, jeża zamiast dokumentów).
Getter
Getter — to publiczna metoda (public), która zwraca wartość prywatnego pola (private). Zwykle jego nazwa zaczyna się od get + nazwa pola z wielkiej litery.
public class Person {
private String name; // Pole prywatne
// Getter dla pola name
public String getName() {
return name;
}
}
Setter
Setter — to publiczna metoda (public), która pozwala zmienić wartość prywatnego pola. Jego nazwa zaczyna się od set + nazwa pola z wielkiej litery.
public class Person {
private String name;
// Setter dla pola name
public void setName(String name) {
this.name = name;
}
}
Dla pól typu boolean
Dla pól typu boolean przyjęto używać prefiksu is w getterach:
private boolean active;
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
2. Składnia i konwencje nazewnicze
Java to język rygorystyczny, ale nie maruda. Istnieją utrwalone konwencje, które sprawiają, że twój kod jest zrozumiały dla innych programistów (i dla ciebie samego za miesiąc).
- Getter: public Type getFieldName()
- Setter: public void setFieldName(Type value)
- Getter dla boolean: public boolean isFieldName()
Nazwa pola w metodzie pisana jest wielką literą: jeśli pole to age, to metody będą getAge() i setAge().
Ta konwencja wspiera styl JavaBeans, dzięki któremu IDE, biblioteki i frameworki mogą automatycznie odnajdywać twoje gettery i settery. Na przykład, jeśli używasz Springa lub JavaFX, te metody będą „magicznie” wywoływane, kiedy będzie to potrzebne.
3. Przykłady kodu
Weźmy projekt edukacyjny — prostą aplikację „kontakty” (odpowiednik książki telefonicznej) — i dodajmy do niej poprawne gettery i settery.
Przykład: klasa Contact z prywatnymi polami i getterami/setterami
public class Contact {
private String name;
private String phone;
private int age;
private boolean favorite;
// Gettery
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public int getAge() {
return age;
}
public boolean isFavorite() {
return favorite;
}
// Settery
public void setName(String name) {
this.name = name;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setAge(int age) {
// Przykład walidacji: wiek nie może być ujemny
if (age < 0) {
System.out.println("Wiek nie może być ujemny!");
return;
}
this.age = age;
}
public void setFavorite(boolean favorite) {
this.favorite = favorite;
}
}
Użycie w aplikacji
Contact friend = new Contact();
friend.setName("Ivan Ivanov");
friend.setPhone("+1-999-123-45-67");
friend.setAge(25);
friend.setFavorite(true);
System.out.println("Imię: " + friend.getName());
System.out.println("Telefon: " + friend.getPhone());
System.out.println("Wiek: " + friend.getAge());
System.out.println("Ulubiony: " + (friend.isFavorite() ? "Tak" : "Nie"));
Przykład walidacji w setterze
Zwróć uwagę, że w setterze setAge dodaliśmy prostą weryfikację: jeśli wiek jest ujemny, nie zmieniamy pola i wypisujemy ostrzeżenie. To prosty sposób na ochronę obiektu przed niepoprawnymi danymi.
4. Najlepsze praktyki: jak robić to poprawnie
Nie wszystkie pola powinny mieć publiczne settery
Czasem pole powinno być tylko do odczytu — na przykład unikalny identyfikator ustawiany przy tworzeniu obiektu i już niezmieniany. W takim przypadku settera po prostu się nie pisze:
public class Contact {
private final int id; // final — nie można zmienić po inicjalizacji
public Contact(int id) {
this.id = id;
}
public int getId() {
return id;
}
// Brak setId!
}
Używaj getterów/setterów do kontroli dostępu i walidacji
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
System.out.println("Imię nie może być puste!");
return;
}
this.name = name;
}
Nie ujawniaj bezpośrednio wewnętrznych obiektów modyfikowalnych
Jeśli masz pole — na przykład listę telefonów:
private String[] phones;
Nie należy zwracać go bezpośrednio przez getter:
public String[] getPhones() {
return phones; // Źle!
}
Taki kod pozwala zewnętrznemu kodowi zmieniać listę jak tylko chce — naruszając enkapsulację!
Lepiej: zwracać kopię tablicy:
public String[] getPhones() {
return Arrays.copyOf(phones, phones.length); // Zwracamy kopię
}
Albo po prostu sklonować:
public String[] getPhones() {
return phones.clone();
}
Rób gettery i settery zrozumiałe i proste
- Nie umieszczaj skomplikowanej logiki biznesowej w getterach/setterach — ich zadanie jest proste: kontrola dostępu i, w razie potrzeby, walidacja.
- Jeśli pole nie powinno się zmieniać — w ogóle nie twórz settera.
- Jeśli pole nie powinno być dostępne z zewnątrz — nie twórz gettera.
5. Automatyczne generowanie getterów/setterów w IDE
Pisanie akcesorów ręcznie to średnia przyjemność, zwłaszcza gdy klasa ma dziesięć pól. Na szczęście nowoczesne IDE (np. IntelliJ IDEA, Eclipse, VS Code z wtyczkami) potrafią generować je automatycznie.
W IntelliJ IDEA
- Otwórz klasę, umieść kursor wewnątrz ciała klasy.
- Naciśnij Alt + Insert (albo Code -> Generate...).
- Wybierz Getter and Setter.
- Zaznacz wymagane pola i kliknij OK.
Voilà! Twoje gettery i settery pojawią się jak za dotknięciem czarodziejskiej różdżki.
W Eclipse
- Otwórz klasę.
- Prawy przycisk myszy — Source — Generate Getters and Setters...
- Wybierz pola i kliknij OK.
W VS Code (z Java Extension Pack)
- Otwórz plik klasy.
- W palecie poleceń (Ctrl+Shift+P) wpisz Generate getters and setters.
- Postępuj zgodnie z podpowiedziami.
6. Rozwój twojej aplikacji: enkapsulacja w praktyce
W poprzednich lekcjach budowałeś prostą aplikację do przechowywania kontaktów. Teraz możemy ją ulepszyć, ustawiając pola jako prywatne i zapewniając dostęp wyłącznie przez gettery/settery.
Było (zły przykład):
public class Contact {
public String name;
public String phone;
public int age;
}
Problem: dowolny kod może zrobić tak:
Contact c = new Contact();
c.age = -1000; // Teraz mamy wampira w książce telefonicznej!
Po zmianie (dobry przykład):
public class Contact {
private String name;
private String phone;
private int age;
public void setAge(int age) {
if (age < 0) {
System.out.println("Wiek nie może być ujemny!");
return;
}
this.age = age;
}
public int getAge() {
return age;
}
// Pozostałe gettery/settery...
}
Teraz nie da się przypadkowo (ani celowo) zepsuć obiektu z zewnątrz.
7. Gettery/settery dla właściwości wyliczanych i niezmiennych
Czasem wartość nie jest przechowywana w polu, tylko obliczana w locie:
public class Rectangle {
private int width;
private int height;
public int getArea() {
return width * height;
}
}
Setter dla pola powierzchni nie jest potrzebny — nie można jej ustawić bezpośrednio, można tylko zmienić szerokość lub wysokość.
8. Gettery i settery: typowe błędy
Błąd nr 1: Getter/setter narusza enkapsulację.
Jeśli getter zwraca referencję do wewnętrznego obiektu modyfikowalnego (np. listy), kod zewnętrzny może go zmienić, omijając wszelkie weryfikacje. To podważa ideę enkapsulacji.
Błąd nr 2: Setter nie waliduje danych.
Jeśli setter po prostu przypisuje wartość, nie sprawdzając jej, można uzyskać niepoprawny stan obiektu (np. ujemny wiek lub puste imię).
Błąd nr 3: Automatyczne generowanie setterów dla wszystkich pól.
IDE może wygenerować settery dla wszystkich pól, ale to nie zawsze jest właściwe! Na przykład dla identyfikatora (id) setter nie jest potrzebny.
Błąd nr 4: Złożona logika w getterach/setterach.
Gettery i settery powinny być proste. Jeśli pojawia się w nich złożona logika biznesowa, warto wydzielić ją do oddzielnych metod.
Błąd nr 5: Naruszanie konwencji nazewniczych.
Jeśli nazwiesz getter fetchName() zamiast getName(), niektóre frameworki i biblioteki mogą nie rozpoznać go poprawnie.
GO TO FULL VERSION