15. Konstruktory w Javie: new, this(), Page Object i builder danych testowych
Czas czytania: 13 minut

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.

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.

Cecha Konstruktor Metoda
Nazwa dokładnie taka jak nazwa klasy dowolna, zwykle czasownik
Typ zwracany brak, nawet void zawsze jest, choćby void
Kto wywołuje new, this(…), super(…) dowolny kod przez obiekt albo klasę
Dziedziczenie nie jest dziedziczony (JLS 8.8) jest dziedziczona, można ją nadpisać
Modyfikatory tylko dostępu: public, protected, private także static, final, abstract
Ile razy na obiekt raz, przy tworzeniu dowolnie wiele razy

Ź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 new przydziela 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ż do Object.
  • 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 new zwraca 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.

Wywołanie Do czego prowadzi Typowe użycie w testach
this(…) inny konstruktor tej samej klasy domyślne wartości danych testowych bez kopiowania przypisań
super(…) konstruktor klasy nadrzędnej przekazanie WebDriver albo Page do klasy BasePage
brak wywołania kompilator dopisuje super() klasy bez dziedziczenia albo z bezparametrową bazą

Ź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
Błąd Objaw w teście Poprawka
void przed nazwą konstruktora pola mają wartości domyślne, null w nieoczekiwanym miejscu usunąć void, konstruktor nie ma typu zwracanego
brak this przy tej samej nazwie pole null mimo podanego argumentu this.pole = pole albo pole final, wtedy kompilator zgłosi błąd
metoda nadpisywana w konstruktorze bazy NullPointerException w podklasie wywołać metodę po utworzeniu obiektu
ciężka praca w konstruktorze Page Object nie da się stworzyć strony w innym scenariuszu konstruktor tylko sprawdza stronę, akcje w metodach
zniknięcie konstruktora domyślnego błąd kompilacji albo deserializacji JSON dopisać jawny konstruktor bezparametrowy

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.

Zobacz szkolenie z Javy

Poprzednia lekcja14. Programowanie obiektowe: klasy i obiektySpis kursuWszystkie lekcje kursu JavyNastępna lekcja16. Funkcje

Powiązane na blogu Quality Island

Pełna lista źródeł

Co o tym sądzisz?

Dodaj komentarz

Bądź na bieżąco
Strefa QA, portal z eksperckimi artykułami o jakości oprogramowania
Bądź na bieżąco
Szkolenie AI Act w wyrobach medycznych: testy i dokumentacja AI pod MDR
AI Act w wyrobach medycznych
AI Act w wyrobach medycznych: testy i dokumentacja AI pod MDR

1690,00 PLN

30.11.26, 18.01.27, 08.03.27
1 dzień
Szkolenie AI Act w bankowości i ubezpieczeniach: scoring kredytowy, wycena ubezpieczeń i FRIA
AI Act w bankowości i ubezpieczeniach
AI Act w bankowości i ubezpieczeniach: scoring kredytowy, wycena ubezpieczeń i FRIA

1690,00 PLN

27.11.26, 15.01.27, 05.03.27
1 dzień
Szkolenie Testowanie systemów AI wysokiego ryzyka pod AI Act: dokumentacja testowa i QMS dla dostawców
Testowanie systemów AI wysokiego ryzyka pod AI Act
Testowanie systemów AI wysokiego ryzyka pod AI Act: dokumentacja testowa i QMS dla dostawców

2390,00 PLN

26.11.26, 14.01.27, 04.03.27
2 dni
Popularne artykuły
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania, grafika Quality Island
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania
Smoke test: co to jest i czym różni się od sanity testu, grafika Quality Island
Smoke test: co to jest, kiedy go uruchamiać i czym różni się od sanity testu
Jak zostać testerem oprogramowania: ścieżka krok po kroku, grafika Quality Island
Jak zostać testerem oprogramowania: ścieżka krok po kroku bez dyplomu informatyka
Najnowsze artykuły
Agentic testing w pracy zespołu QA, co się zmienia w testowaniu z agentami AI
Agentic testing: czy Wasza praca właśnie się zmienia, czy znika
Ile kosztują testy oprogramowania, cennik i czynniki ceny, Quality Island
Ile kosztują testy oprogramowania: cennik i czynniki ceny
Strategia testowania w organizacji, strategia jakości na przykładzie case PKO BP, Quality Island
Strategia testowania w organizacji: czego uczy nas case PKO BP
Popularne kategorie