1. Składnia przeciążania konstruktorów
W życiu rzadko bywa tak, że wszystkie obiekty tworzy się jednakowo. Wyobraź sobie klasę Person (Człowiek). Czasem chcemy utworzyć osobę, znając tylko jej imię. Czasem — imię i wiek. A czasem — nie wiemy nic i niech wszystko będzie domyślne. Byłoby dziwne zmuszać użytkownika klasy do zawsze podawania wszystkich parametrów, nawet jeśli nie są mu potrzebne.
Przeciążanie konstruktorów to sposób, by dać użytkownikowi klasy wybór: które parametry chce podać przy tworzeniu obiektu, a które pozostawić domyślne. Dzięki temu klasa jest elastyczna i wygodna w użyciu.
Analogia:
W biurze konstrukcyjnym buduje się samochody. Ktoś zamawia wersję podstawową (bez klimatyzacji, tylko kierownica i koła), ktoś inny chce „lux” (z podgrzewanymi siedzeniami, Wi‑Fi i żeby samochód jeździł sam). Ale samochód to wciąż ta sama klasa — po prostu różne sposoby jego złożenia!
Przeciążanie oznacza, że w jednej klasie zadeklarowano kilka konstruktorów, lecz z różnymi parametrami (inna liczba, typ lub kolejność parametrów). Wszystkie nazywają się tak samo jak klasa i nie mają typu zwracanego.
Przykład: klasa z przeciążonymi konstruktorami
public class Person {
String name;
int age;
// Konstruktor bez parametrów (domyślny)
public Person() {
this.name = "Nieznane";
this.age = 0;
}
// Konstruktor z jednym parametrem
public Person(String name) {
this.name = name;
this.age = 0;
}
// Konstruktor z dwoma parametrami
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Teraz możemy tworzyć obiekty na różne sposoby:
Person p1 = new Person(); // imię = "Nieznane", wiek = 0
Person p2 = new Person("John"); // imię = "John", wiek = 0
Person p3 = new Person("Bob", 25); // imię = "Bob", wiek = 25
Jak Java rozumie, którego konstruktora użyć?
Java patrzy na liczbę i typy argumentów przekazywanych po new. Jeśli piszesz new Person("John"), kompilator szuka konstruktora z jednym parametrem typu String. Jeśli piszesz new Person("Bob", 25), potrzebny jest konstruktor z parametrami String i int.
2. Wywołanie jednego konstruktora z innego: this(...)
Czasem przy przeciążaniu konstruktorów część kodu się powtarza. Na przykład chcesz, aby wszystkie konstruktory zawsze ustawiał y imię, a wiek, jeśli nie podany, wynosił zero. Aby nie kopiować tej samej logiki w każdym konstruktorze, można wywołać jeden konstruktor z innego za pomocą słowa kluczowego this(...).
Przykład
public class Person {
String name;
int age;
// Konstruktor z dwoma parametrami
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Konstruktor z jednym parametrem wywołuje inny konstruktor
public Person(String name) {
this(name, 0); // wywołuje Person(String name, int age)
}
// Konstruktor bez parametrów wywołuje inny konstruktor
public Person() {
this("Nieznane", 0);
}
}
public Person(String name) {
this(name, 0); // wywołuje Person(String, int)
}
Ważna zasada: Wywołanie innego konstruktora przez this(...) musi być pierwszą linią konstruktora!
Po co to jest potrzebne?
- Zmniejsza duplikację kodu.
- Jeśli zdecydujesz się zmienić logikę inicjalizacji (np. wartość domyślną), trzeba będzie to zrobić tylko w jednym miejscu.
- Kod staje się czystszy i łatwiejszy w utrzymaniu.
3. Przykłady praktyczne: przeciążanie w rzeczywistej klasie
Załóżmy, że mamy klasę Account — konto bankowe. Czasem znamy tylko nazwę właściciela, czasem chcemy od razu podać saldo początkowe, a czasem także walutę.
Przykład klasy z przeciążonymi konstruktorami
public class Account {
String owner;
double balance;
String currency;
// Konstruktor z trzema parametrami
public Account(String owner, double balance, String currency) {
this.owner = owner;
this.balance = balance;
this.currency = currency;
}
// Konstruktor z dwoma parametrami (waluta domyślna — "EUR")
public Account(String owner, double balance) {
this(owner, balance, "EUR");
}
// Konstruktor z jednym parametrem (saldo = 0, waluta = "EUR")
public Account(String owner) {
this(owner, 0.0, "EUR");
}
// Konstruktor bez parametrów (właściciel — "Nieznane", saldo = 0, waluta = "EUR")
public Account() {
this("Nieznane", 0.0, "EUR");
}
public void printInfo() {
System.out.println(owner + ": " + balance + " " + currency);
}
}
Użycie
public class Main {
public static void main(String[] args) {
Account acc1 = new Account("John", 1000, "USD");
Account acc2 = new Account("Mary", 500);
Account acc3 = new Account("Peter");
Account acc4 = new Account();
acc1.printInfo(); // John: 1000.0 USD
acc2.printInfo(); // Mary: 500.0 EUR
acc3.printInfo(); // Peter: 0.0 EUR
acc4.printInfo(); // Nieznane: 0.0 EUR
}
}
Dlaczego to wygodne?
- Można tworzyć obiekty z różnym poziomem szczegółowości.
- Nie trzeba za każdym razem podawać wszystkich parametrów (zwłaszcza jeśli często są takie same).
- Łatwo rozszerzać klasę: jeśli pojawi się nowy parametr, można dodać kolejny konstruktor.
4. Typowe błędy przy przeciążaniu konstruktorów
Błąd nr 1: Pomyłka w typach i kolejności parametrów.
Jeśli masz dwa konstruktory — Person(String name, int age) i Person(int age, String name) — kompilator je rozróżni, ale dla użytkownika klasy może to być bardzo mylące. Lepiej unikać takich sytuacji.
Błąd nr 2: Brak konstruktora domyślnego.
Jeśli zadeklarujesz tylko konstruktory z parametrami, a potem spróbujesz utworzyć obiekt bez parametrów — dostaniesz błąd kompilacji. Zawsze dodawaj konstruktor bez parametrów, jeśli jest potrzebny.
Błąd nr 3: Próba wywołania innego konstruktora nie w pierwszej linii.
Wywołanie this(...) zawsze musi być pierwszą linią konstruktora. Jeśli napiszesz coś przed tym — będzie błąd kompilacji.
Błąd nr 4: Zapętlenie wywołań konstruktorów.
Jeśli konstruktor wywołuje sam siebie (bezpośrednio lub przez łańcuch), doprowadzi to do błędu kompilacji z powodu nieskończonej rekursji.
Błąd nr 5: Niezinicjalizowane pola.
Jeśli zapomnisz zainicjalizować jakieś pole w konstruktorze (lub w łańcuchu wywołań), obiekt może znaleźć się w niepoprawnym stanie. Upewnij się, że wszystkie pola mają sensowne wartości po utworzeniu obiektu.
GO TO FULL VERSION