Konstruktory w Javie to bloki kodu o nazwie klasy i bez typu zwracanego, które wykonują się przy tworzeniu obiektu operatorem new i ustawiają jego stan początkowy. To piętnasta lekcja kursu Javy dla testerów. Pokazujemy, co naprawdę dzieje się przy new, kiedy kompilator dokłada konstruktor domyślny, jak działa przeciążanie i this(...), a potem przenosimy to do Page Object i do budowania danych testowych.
Starsza wersja tej lekcji nazywała konstruktor „specjalną metodą”, a new() metodą. Oba skróty mylą. Według specyfikacji języka Java SE 25, rozdział 8.8, konstruktory nie są członkami klasy, nie są dziedziczone i nie da się ich nadpisać, a new to operator tworzenia instancji, nie metoda. Cały kod z tej lekcji skompilowaliśmy kompilatorem javac z opcją --release 25 i -Xlint:all bez ostrzeżeń, a wyniki uruchomień wklejamy pod przykładami. Jeśli klasy i obiekty są dla Was jeszcze świeże, zacznijcie od lekcji 14 o klasach i obiektach.
Spis treści
- Czym są konstruktory w Javie i czym różnią się od metod?
- Co dzieje się krok po kroku, gdy piszecie new?
- Kiedy kompilator tworzy konstruktor domyślny?
- Jak działa konstruktor z parametrami i przeciążanie konstruktorów?
- Do czego służą this(…) i super(…) w konstruktorze?
- Jak wygląda konstruktor w Page Object?
- Jak budować dane testowe: builder i rekord zamiast pięciu konstruktorów?
- Jakie błędy z konstruktorami najczęściej łapiemy na code review?
Czym są konstruktory w Javie i czym różnią się od metod?
Konstruktor wygląda jak metoda, ale ma trzy cechy, których metoda mieć nie może: nazywa się dokładnie tak jak klasa, nie deklaruje typu zwracanego (nawet void) i wywołuje go wyłącznie mechanizm tworzenia obiektu: wyrażenie new, wywołanie this(...) albo super(...) z innego konstruktora. Według specyfikacji języka Java SE 25, rozdział 8.8, konstruktory nie są dziedziczone, więc klasa LoginPage nie dostanie automatycznie konstruktora z klasy bazowej, nawet jeśli ten ma identyczną listę parametrów.
Źródło: Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 8.8, 2025
Dla testera najważniejszy wiersz to ostatni. Konstruktor ma jedno zadanie: oddać obiekt w poprawnym stanie, gotowy do użycia. Wszystko, co dzieje się później, czyli klikanie, wysyłanie żądań i asercje, należy do metod i do samego testu.
Co dzieje się krok po kroku, gdy piszecie new?
Pamięć dla obiektu przydziela operator new, nie konstruktor. Według specyfikacji Java SE 25, rozdział 12.5, najpierw powstaje miejsce na wszystkie pola obiektu (także te z klas nadrzędnych) i każde pole dostaje wartość domyślną, a dopiero potem wykonuje się wskazany konstruktor. Wartości domyślne opisuje rozdział 4.12.5 tej samej specyfikacji: 0 dla liczb, false dla boolean, null dla referencji. To ważne sprostowanie, bo popularny skrót „konstruktor domyślny zeruje pola” jest nieprawdziwy: pola są wyzerowane, zanim jakikolwiek konstruktor zacznie działać.
- Krok 1. Operator
newprzydziela pamięć, pola mają wartości domyślne. - Krok 2. Argumenty trafiają do parametrów konstruktora. Jeśli pierwszą instrukcją jest
this(...), proces startuje od tamtego konstruktora. - Krok 3. Wywołanie
super(...), jawne albo dopisane przez kompilator, przechodzi w górę hierarchii aż doObject. - Krok 4. Wykonują się inicjalizatory pól i bloki inicjalizacyjne tej klasy, w kolejności z kodu.
- Krok 5. Wykonuje się reszta ciała konstruktora, a
newzwraca referencję do gotowego obiektu.
Kolejność najlepiej zobaczyć na kodzie. Klasa Strona dziedziczy po Baza, a konstruktor klasy bazowej woła metodę, którą podklasa nadpisuje:
package k15;
public class Kolejnosc {
static class Baza {
Baza() {
System.out.println("2. konstruktor klasy Baza");
opisz(); // wywołanie metody, którą nadpisuje podklasa
}
void opisz() {
}
}
static class Strona extends Baza {
private String adres = "https://example.com/logowanie"; // inicjalizator pola
private int timeout;
Strona() {
super();
System.out.println("3. inicjalizatory pól już wykonane, adres = " + adres);
timeout = 10;
System.out.println("4. ciało konstruktora Strona, timeout = " + timeout);
}
@Override
void opisz() {
System.out.println(" opisz() z podklasy: adres = " + adres + ", timeout = " + timeout);
}
}
public static void main(String[] args) {
System.out.println("1. new Strona(): pamięć przydzielona, pola mają wartości domyślne");
new Strona();
}
}
1. new Strona(): pamięć przydzielona, pola mają wartości domyślne
2. konstruktor klasy Baza
opisz() z podklasy: adres = null, timeout = 0
3. inicjalizatory pól już wykonane, adres = https://example.com/logowanie
4. ciało konstruktora Strona, timeout = 10
Wbrew pozorom
Pole adres ma w kodzie wartość wpisaną przy deklaracji, a mimo to metoda opisz() widzi null. Według specyfikacji Java SE 25, rozdział 12.5, inicjalizatory pól podklasy wykonują się dopiero po powrocie z konstruktora klasy nadrzędnej, a metoda nadpisana jest wywoływana już wcześniej. Dlatego w konstruktorze klasy bazowej nie wołajcie metod, które podklasa może nadpisać. W testach kończy się to zwykle NullPointerException w miejscu, które na pierwszy rzut oka nie może dać nulla.
Kiedy kompilator tworzy konstruktor domyślny?
Konstruktor domyślny dopisuje kompilator tylko wtedy, gdy klasa nie deklaruje żadnego konstruktora. Według specyfikacji Java SE 25, rozdział 8.8.9, ma on ten sam modyfikator dostępu co klasa, nie przyjmuje parametrów i robi jedną rzecz: wywołuje bezparametrowy konstruktor klasy nadrzędnej. Nie ustawia żadnych wartości pól, bo te są już domyślne po kroku 1.
Pułapka pojawia się w chwili, gdy dopiszecie pierwszy własny konstruktor z parametrami. Konstruktor domyślny wtedy znika, a każde new Konfiguracja() w testach przestaje się kompilować:
public class Konfiguracja {
private final String przegladarka;
public Konfiguracja(String przegladarka) {
this.przegladarka = przegladarka;
}
public static void main(String[] args) {
Konfiguracja k = new Konfiguracja();
}
}
Konfiguracja.java:9: error: constructor Konfiguracja in class Konfiguracja cannot be applied to given types;
Konfiguracja k = new Konfiguracja();
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
1 error
Jeśli bezparametrowy wariant jest potrzebny, na przykład dla biblioteki, która tworzy obiekty refleksją, musicie go dopisać sami. Tak działają między innymi niektóre biblioteki do mapowania JSON na obiekty, dlatego klasa danych testowych bez konstruktora bezparametrowego potrafi wywrócić deserializację odpowiedzi API.
Jak działa konstruktor z parametrami i przeciążanie konstruktorów?
Konstruktor z parametrami przyjmuje dane potrzebne do zbudowania obiektu, a przeciążanie pozwala mieć kilka konstruktorów o różnych listach parametrów. Kompilator wybiera wersję po liczbie i typach argumentów, tak samo jak przy przeciążonych metodach. Poniżej klasa użytkownika testowego z trzema konstruktorami: pełnym, z domyślną rolą i bezparametrowym dla szybkich scenariuszy.
package k15;
public class Uzytkownik {
private final String login;
private final String haslo;
private final String rola;
// konstruktor z pełnym zestawem parametrów
public Uzytkownik(String login, String haslo, String rola) {
this.login = login;
this.haslo = haslo;
this.rola = rola;
}
// przeciążony konstruktor: domyślna rola, reszta przez this(...)
public Uzytkownik(String login, String haslo) {
this(login, haslo, "klient");
}
// konstruktor bezparametrowy: konto testowe do szybkich scenariuszy
public Uzytkownik() {
this("tester@example.com", "Haslo123!");
}
@Override
public String toString() {
return login + " (" + rola + ")";
}
public static void main(String[] args) {
System.out.println(new Uzytkownik("admin@example.com", "Adm1n!", "administrator"));
System.out.println(new Uzytkownik("jan@example.com", "Jan123!"));
System.out.println(new Uzytkownik());
}
}
admin@example.com (administrator)
jan@example.com (klient)
tester@example.com (klient)
Pola są final, więc kompilator pilnuje, żeby każdy konstruktor przypisał każde z nich dokładnie raz. To tania ochrona przed obiektem w połowie zainicjalizowanym. Więcej o tym, dlaczego pola mają tu modyfikator private, znajdziecie w lekcji 17 o hermetyzacji w Javie.
Do czego służą this(…) i super(…) w konstruktorze?
Wywołanie this(...) przekazuje pracę do innego konstruktora tej samej klasy, a super(...) do konstruktora klasy nadrzędnej. Dzięki this(...) logika przypisań żyje w jednym miejscu: w klasie Uzytkownik dwa krótsze konstruktory tylko uzupełniają brakujące wartości i oddają sterowanie pełnemu.
Do Javy 24 takie wywołanie musiało być pierwszą instrukcją w ciele konstruktora. Niezmiennie może być tylko jedno: albo this(...), albo super(...). Jeśli nie napiszecie żadnego, kompilator wstawi super() bez argumentów. Według strony JEP 513 w OpenJDK od Javy 25 wolno przed super(...) umieścić instrukcje, które nie dotykają tworzonego obiektu, na przykład walidację argumentów. Kurs prowadzimy na Javie 25, więc możecie z tego korzystać, ale w przykładach trzymamy się klasycznej kolejności, bo zadziała też w projektach na starszej Javie. Przykład walidacji przed super(...) pokazujemy w lekcji 18 o dziedziczeniu.
Źródło: Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 8.8.7, 2025; OpenJDK, JEP 513, 2025
O tym, jak super(...) łączy się z dziedziczeniem i nadpisywaniem metod, opowiada lekcja 18 o dziedziczeniu.
Jak wygląda konstruktor w Page Object?
W Page Object konstruktor przyjmuje sterownik przeglądarki, zapamiętuje go w polu i sprawdza, czy test faktycznie jest na właściwej stronie. Według dokumentacji Selenium (sekcja Page Object Models, 2025) to jedyna weryfikacja, która powinna mieszkać w klasie strony, i ma się odbyć właśnie przy tworzeniu obiektu. Asercje biznesowe zostają w teście.
package k15;
import java.time.Duration;
import java.util.Objects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
abstract class BasePage {
protected final WebDriver driver;
protected final WebDriverWait wait;
protected BasePage(WebDriver driver) {
this.driver = Objects.requireNonNull(driver, "driver nie może być null");
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
}
public class LoginPage extends BasePage {
private final By email = By.id("username");
private final By haslo = By.id("password");
private final By zaloguj = By.name("login");
public LoginPage(WebDriver driver) {
super(driver);
// konstruktor sprawdza tylko, czy jesteśmy na właściwej stronie
wait.until(ExpectedConditions.visibilityOfElementLocated(email));
}
public void zalogujJako(String login, String haslo) {
driver.findElement(email).sendKeys(login);
driver.findElement(this.haslo).sendKeys(haslo);
driver.findElement(zaloguj).click();
}
}
Kilka decyzji, które widać w tym kodzie. Objects.requireNonNull zatrzymuje test od razu z czytelnym komunikatem, zamiast pozwolić mu wyłożyć się trzy kroki dalej. Lokatory to pola final, bo nie zmieniają się w trakcie życia strony. Konstruktor LoginPage czeka maksymalnie 10 sekund na pole e-mail, a nie loguje się sam: logowanie to akcja, więc jest metodą. Kod kompilowaliśmy z Selenium 4.51.0, aktualnym wydaniem w Maven Central na 10.10.2026. W Selenium 4 nie musicie też ręcznie pobierać sterownika, bo od wersji 4.6 robi to Selenium Manager.
Z naszego doświadczenia najczęstszy problem w Page Object to konstruktor, który robi za dużo: otwiera przeglądarkę, loguje się i przechodzi trzy ekrany dalej. Taki obiekt da się stworzyć tylko w jednym scenariuszu, a każdy błąd po drodze wygląda jak błąd konstruktora. Szerzej o wzorcach w testach UI piszemy w artykule o wzorcach projektowych w Selenium WebDriver, a całą ścieżkę z trenerem przechodzicie na szkoleniu automatyzacja testów Selenium WebDriver.
Jak budować dane testowe: builder i rekord zamiast pięciu konstruktorów?
Gdy obiekt danych testowych ma pięć pól, a część z nich jest opcjonalna, przeciążanie szybko się wymyka: powstaje szereg konstruktorów o rosnącej liczbie parametrów i nikt nie pamięta, który String jest którym. Joshua Bloch w książce Effective Java (wydanie 3, 2018, temat 2) nazywa to wzorcem teleskopowych konstruktorów i proponuje w zamian budowniczego (builder). W testach builder ma dodatkową zaletę: każde pole ma sensowną wartość domyślną, a test nadpisuje tylko to, co dla niego istotne.
package k15;
public record KlientTestowy(String email, String kraj, int wiek) {
// konstruktor kompaktowy rekordu: walidacja bez powtarzania przypisań
public KlientTestowy {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("niepoprawny email: " + email);
}
if (wiek < 0) {
throw new IllegalArgumentException("wiek nie może być ujemny");
}
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private String email = "klient@example.com";
private String kraj = "PL";
private int wiek = 30;
private Builder() {
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder kraj(String kraj) {
this.kraj = kraj;
return this;
}
public Builder wiek(int wiek) {
this.wiek = wiek;
return this;
}
public KlientTestowy build() {
return new KlientTestowy(email, kraj, wiek);
}
}
public static void main(String[] args) {
System.out.println(KlientTestowy.builder().build());
System.out.println(KlientTestowy.builder().kraj("DE").wiek(17).build());
try {
KlientTestowy.builder().email("bez-malpy").build();
} catch (IllegalArgumentException e) {
System.out.println("odrzucone: " + e.getMessage());
}
}
}
KlientTestowy[email=klient@example.com, kraj=PL, wiek=30]
KlientTestowy[email=klient@example.com, kraj=DE, wiek=17]
odrzucone: niepoprawny email: bez-malpy
Klasa jest rekordem, typem wprowadzonym na stałe w Javie 16 według JEP 395. Rekord sam generuje konstruktor kanoniczny, gettery, equals i toString, a my dopisujemy tylko konstruktor kompaktowy z walidacją. Konstruktor buildera jest prywatny, więc jedyna droga do niego prowadzi przez KlientTestowy.builder(). Test builder().kraj("DE").wiek(17).build() czyta się jak opis przypadku testowego: niepełnoletni klient z Niemiec.
Przeciążone konstruktory
Do 3 parametrów
Krótko i czytelnie, dopóki parametry mają różne typy. Dobre dla prostych obiektów konfiguracji.
Builder
Wiele pól opcjonalnych
Wartości domyślne plus nadpisanie tylko tego, co ważne dla testu. Standard dla danych testowych.
Rekord
Dane niezmienne
Konstruktor, gettery, equals i toString za darmo, walidacja w konstruktorze kompaktowym.
Jakie błędy z konstruktorami najczęściej łapiemy na code review?
Większość błędów z konstruktorami kompiluje się bez słowa ostrzeżenia, dlatego warto znać je z wyglądu. Oto dwa, które w naszych projektach wracają najczęściej. Pierwszy to void przed nazwą konstruktora. Kompilator uznaje taki kod za zwykłą metodę o dziwnej nazwie, a obiekt powstaje przez niewidoczny konstruktor domyślny:
package k15;
public class MetodaZamiastKonstruktora {
private String przegladarka;
// UWAGA: void sprawia, że to zwykła metoda, a nie konstruktor
public void MetodaZamiastKonstruktora() {
przegladarka = "chrome";
}
public static void main(String[] args) {
MetodaZamiastKonstruktora konfiguracja = new MetodaZamiastKonstruktora();
System.out.println("przegladarka = " + konfiguracja.przegladarka);
}
}
przegladarka = null
Drugi to parametr o tej samej nazwie co pole, przypisany bez this. Parametr przesłania pole, więc linijka przypisuje wartość parametru jemu samemu, a pole zostaje null. To błąd tak cichy, że kompilator nie mrugnie nawet okiem, a test i tak wywróci się dopiero na staging.
package k15;
public class Cien {
private String srodowisko;
public Cien(String srodowisko) {
srodowisko = srodowisko; // przypisanie parametru do samego siebie
}
public static void main(String[] args) {
System.out.println("srodowisko = " + new Cien("staging").srodowisko);
}
}
srodowisko = null
Na szkoleniach, na których szkolimy testerów wchodzących w automatyzację, drugi błąd wychodzi najczęściej, bo IDE podpowiada nazwy parametrów identyczne z polami. Według danych Quality Island przeszkoliliśmy już ponad 2 500 specjalistów, a nawyk deklarowania pól jako final to jedna z pierwszych rad, jakie od nas słyszą. Jeśli chcecie przerobić programowanie obiektowe w Javie z trenerem, służy do tego szkolenie programowanie w Javie dla testerów oprogramowania, a dla osób, które zaczynają od zera, wprowadzenie do programowania od podstaw w Javie. Więcej materiałów o praktyce testowania znajdziecie też na portalu Strefa QA, gdzie piszemy o jakości oprogramowania od strony zespołów i decydentów.
Co zabrać z tego artykułu
01Konstruktor nie jest metodą: ma nazwę klasy, nie ma typu zwracanego, nie jest dziedziczony. new to operator, nie metoda.
02Pamięć i wartości domyślne pól daje new, zanim ruszy jakikolwiek konstruktor. Konstruktor domyślny nie zeruje pól.
03Konstruktor domyślny istnieje tylko w klasie bez własnych konstruktorów. Pierwszy konstruktor z parametrami go usuwa.
04Do Javy 24 this(…) i super(…) musiały być pierwszą instrukcją; od Javy 25 (JEP 513) wolno przed nimi na przykład sprawdzić argumenty. Nie wołajcie w konstruktorze bazy metod, które podklasa nadpisuje.
05W Page Object konstruktor przyjmuje driver i sprawdza stronę. Dane testowe budujcie builderem albo rekordem z walidacją.
Chcecie, żeby Wasi testerzy pisali w Javie Page Object i dane testowe, które da się utrzymać? Na szkoleniu przerobicie klasy, konstruktory i dziedziczenie na przykładach z automatyzacji.
Powiązane na blogu Quality Island
- 14. Programowanie obiektowe: klasy i obiekty
- 16. Funkcje w Javie
- 17. Hermetyzacja w Javie: modyfikatory dostępu, rekordy i Page Object
- 18. Dziedziczenie
- Java dla testerów automatyzujących: jak zacząć naukę
Pełna lista źródeł
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 8.8 Constructor Declarations, 2025. Konstruktory nie są członkami klasy ani nie są dziedziczone, konstruktor domyślny (8.8.9), wywołania this i super (8.8.7).
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 12.5 Creation of New Class Instances, 2025. Kolejność: przydział pamięci, wartości domyślne, konstruktor, inicjalizatory pól.
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 4.12.5 Initial Values of Variables, 2025.
- Oracle, The Java Tutorials: Providing Constructors for Your Classes, 2024.
- OpenJDK, JEP 395: Records, 2021. Rekordy na stałe w Javie 16.
- OpenJDK, JEP 513: Flexible Constructor Bodies, 2025. Instrukcje przed super(…) od Javy 25.
- Selenium, Page Object Models, dokumentacja Selenium, 2025. Weryfikacja strony w konstruktorze, asercje w teście.
- Joshua Bloch, Effective Java, wydanie 3, Addison-Wesley, 2018. Temat 2: builder zamiast teleskopowych konstruktorów.
- Quality Island, dane własne: ponad 2 500 przeszkolonych specjalistów, stan na 2026.