Dziedziczenie w Javie to mechanizm, w którym jedna klasa przejmuje pola i metody innej klasy przez słowo kluczowe extends, a potem może je rozszerzać albo nadpisywać. Klasa podrzędna jest szczególnym przypadkiem klasy bazowej: tester jest pracownikiem, strona logowania jest stroną.
To osiemnasta lekcja kursu Javy dla testerów. Pod filmem znajdziecie pełną lekcję tekstową: dziedziczenie Java krok po kroku z extends i super, nadpisywanie metod z @Override, różnicę między klasą abstrakcyjną a interfejsem, klasy zapieczętowane (sealed) z Javy 17, zasadę „kompozycja zamiast dziedziczenia” i dwie klasy, które spotkacie w każdym frameworku testów: BaseTest i BasePage. Cały kod skompilowaliśmy poleceniem javac --release 25, a przykłady bez przeglądarki również uruchomiliśmy.
Spis treści
- Czym jest dziedziczenie w Javie?
- Dziedziczenie Java w praktyce: extends i super
- Jak nadpisać metodę i po co jest @Override?
- Klasa abstrakcyjna czy interfejs?
- Czym są klasy zapieczętowane (sealed) z Javy 17?
- Kiedy kompozycja jest lepsza niż dziedziczenie?
- Dziedziczenie w testach: BaseTest i BasePage
- Jakie błędy zgłosi kompilator?
- Ćwiczenie z rozwiązaniem
Czym jest dziedziczenie w Javie?
Dziedziczenie to jeden z czterech filarów programowania obiektowego, obok hermetyzacji, abstrakcji i polimorfizmu. Klasa, po której się dziedziczy, to klasa bazowa (nadklasa, rodzic). Klasa, która dziedziczy, to podklasa (klasa pochodna, dziecko). Według specyfikacji języka Java SE 25 (rozdział 8.1.4) każda klasa ma dokładnie jedną bezpośrednią nadklasę, a jeśli jej nie wskażecie, jest nią java.lang.Object. Dlatego każdy obiekt w Javie ma metody toString, equals i hashCode, choć ich nie pisaliście.

Test, który warto sobie zadawać przed każdym extends, to pytanie „czy B jest rodzajem A?”. Tester jest pracownikiem, więc Tester extends Pracownik ma sens. Klient API nie jest loggerem, tylko z loggera korzysta, więc tu dziedziczenie byłoby błędem, o czym więcej w sekcji o kompozycji.
Dziedziczenie w Javie w liczbach
1
bezpośrednia nadklasa każdej klasy, Java nie ma wielodziedziczenia klas
17
wersja Javy, w której klasy sealed weszły na stałe
21
wersja LTS, w której switch po typach sealed jest standardem
450 000+
testów automatycznych napisanych w Quality Island
Źródła: Java Language Specification SE 25, rozdział 8.1.4, 2025; OpenJDK JEP 409, 2021; OpenJDK JEP 441, 2023; dane własne Quality Island 2026.
Dziedziczenie Java w praktyce: extends i super
Podklasę deklarujecie słowem extends, a konstruktor klasy bazowej wywołujecie przez super(...). Do Javy 24 specyfikacja (rozdział 8.8.7) wymagała, żeby wywołanie super było pierwszą instrukcją konstruktora. Od Javy 25, na której prowadzimy kurs, według dokumentu OpenJDK JEP 513 przed super(...) wolno umieścić instrukcje, które nie korzystają z tworzonego obiektu, na przykład walidację argumentów. Przykład jest niżej, pod klasą Tester. Jeśli go nie napiszecie, kompilator sam wstawi super() bez argumentów, co działa tylko wtedy, gdy klasa bazowa ma taki konstruktor.
Pracownik.java, klasa bazowa
public class Pracownik {
protected final String imie;
private final int stawkaGodzinowa;
public Pracownik(String imie, int stawkaGodzinowa) {
this.imie = imie;
this.stawkaGodzinowa = stawkaGodzinowa;
}
public int wyplata(int godziny) {
return stawkaGodzinowa * godziny;
}
public String przedstawSie() {
return "Jestem " + imie;
}
}
Tester.java, podklasa
public class Tester extends Pracownik {
private final int dodatekZaCertyfikat;
public Tester(String imie, int stawkaGodzinowa, int dodatekZaCertyfikat) {
super(imie, stawkaGodzinowa); // najpierw stan klasy bazowej
this.dodatekZaCertyfikat = dodatekZaCertyfikat;
}
@Override
public int wyplata(int godziny) {
return super.wyplata(godziny) + dodatekZaCertyfikat;
}
@Override
public String przedstawSie() {
return super.przedstawSie() + " i testuję oprogramowanie"; // wersja z klasy bazowej plus dopisek
}
}
Od Javy 25 konstruktor podklasy może sprawdzić argumenty, zanim wywoła super(...). Dzięki temu błędne dane zatrzymacie od razu, bez tworzenia obiektu klasy bazowej:
TesterJep513.java, Java 25 (JEP 513)
public class TesterJep513 extends Pracownik {
private final int dodatekZaCertyfikat;
public TesterJep513(String imie, int stawkaGodzinowa, int dodatekZaCertyfikat) {
if (dodatekZaCertyfikat < 0) { // od Javy 25 wolno przed super(...)
throw new IllegalArgumentException("Dodatek nie może być ujemny: " + dodatekZaCertyfikat);
}
super(imie, stawkaGodzinowa);
this.dodatekZaCertyfikat = dodatekZaCertyfikat;
}
}
Skompilowane z --release 25 działa: new TesterJep513("Ola", 100, -1) rzuca IllegalArgumentException. Ten sam plik z --release 21 kończy się błędem flexible constructors is not supported in -source 21, więc w projekcie na starszej Javie walidację przenieście do metody statycznej wywołanej w argumencie super(...). Oba wyniki sprawdziliśmy w JDK 25 11.10.2026.
DziedziczenieDemo.java, wynik w komentarzach
import java.util.List;
public class DziedziczenieDemo {
public static void main(String[] args) {
Pracownik anna = new Pracownik("Anna", 100);
Tester piotr = new Tester("Piotr", 100, 500);
System.out.println(anna.przedstawSie()); // Jestem Anna
System.out.println(piotr.przedstawSie()); // Jestem Piotr i testuję oprogramowanie
System.out.println(piotr.wyplata(160)); // 16500
// polimorfizm: zmienna typu bazowego, obiekt podklasy
List<Pracownik> zespol = List.of(anna, piotr);
int suma = 0;
for (Pracownik p : zespol) {
suma += p.wyplata(160); // Java wywoła wersję z klasy obiektu
}
System.out.println(suma); // 32500
System.out.println(piotr instanceof Pracownik); // true
}
}
Zwróćcie uwagę na trzy rzeczy. Pole imie jest protected, więc podklasa może z niego korzystać, a pole stawkaGodzinowa jest prywatne i Tester sięga po nie tylko przez metodę super.wyplata. Pętla po liście List<Pracownik> wywołuje dla Piotra wersję metody z klasy Tester, bo Java wybiera metodę według klasy obiektu, a nie typu zmiennej. To jest polimorfizm i to dzięki niemu framework testów może trzymać różne strony pod jednym typem BasePage. Szczegóły modyfikatora protected i jego pułapek omawia poprzednia lekcja, 17. Hermetyzacja w Javie, a samo this i kolejność wywołań konstruktorów lekcja 15. Konstruktory.
Jak nadpisać metodę i po co jest @Override?
Nadpisanie (override) to napisanie w podklasie metody o tej samej nazwie i tych samych typach parametrów co w klasie bazowej. Adnotacja @Override nie jest obowiązkowa, ale według dokumentacji API Java SE 25 (wersja z 2025 roku) każe kompilatorowi sprawdzić, czy metoda naprawdę coś nadpisuje. Bez niej literówka w nazwie albo inny typ parametru tworzy po cichu nową metodę, a stara dalej działa.
Źródło: Oracle, The Java Tutorials: Overriding and Hiding Methods; Java Language Specification SE 25, 2025, rozdział 8.4.8.
W kodzie testów @Override ratuje przed bardzo podstępnym błędem. Metoda jestOtwarta() w BasePage i przypadkowa jestOtwarte() w podklasie to dwie różne metody. Test woła wersję z klasy bazowej i sprawdza coś innego, niż myślicie. Z adnotacją kompilator zatrzyma Was od razu, co pokazujemy w tabeli błędów niżej.
Klasa abstrakcyjna czy interfejs?
Klasa abstrakcyjna to klasa, z której nie da się utworzyć obiektu i która może mieć metody bez ciała, do uzupełnienia w podklasach. Interfejs opisuje zestaw zachowań, które klasa obiecuje dostarczyć. Klasa może rozszerzać tylko jedną klasę, ale implementować wiele interfejsów, i to zwykle rozstrzyga wybór.
Źródło: Oracle, The Java Tutorials: Abstract Methods and Classes; Java Language Specification SE 25, 2025, rozdziały 8.1.1.1 i 9.
Reguła, którą stosujemy w naszych projektach: zaczynajcie od interfejsu, a do klasy abstrakcyjnej sięgajcie dopiero wtedy, gdy kilka klas naprawdę dzieli stan, na przykład obiekt WebDriver i obiekt oczekiwania. Wtedy klasa abstrakcyjna oszczędza powtarzania tych samych pól i konstruktora w każdej stronie.
Czym są klasy zapieczętowane (sealed) z Javy 17?
Klasa albo interfejs zapieczętowany (sealed) sam wskazuje, kto może po nim dziedziczyć, w klauzuli permits. Według dokumentu OpenJDK JEP 409 funkcja weszła na stałe w Javie 17. Według JEP 441 od Javy 21 instrukcja switch potrafi rozpoznać typ obiektu i sprawdzić, że obsłużyliście wszystkie dozwolone podtypy, więc gałąź default nie jest potrzebna.
Platnosci.java, Java 25, wynik w komentarzach
sealed interface Platnosc permits Karta, Blik, Przelew {}
record Karta(String numer) implements Platnosc {}
record Blik(String kod) implements Platnosc {}
record Przelew(String iban) implements Platnosc {}
public class Platnosci {
static String opis(Platnosc p) {
// switch bez default: kompilator wie, że innych typów płatności nie ma
return switch (p) {
case Karta k -> "karta kończąca się na " + k.numer().substring(k.numer().length() - 4);
case Blik b -> "BLIK " + b.kod();
case Przelew pr -> "przelew z " + pr.iban().substring(0, 2);
};
}
public static void main(String[] args) {
System.out.println(opis(new Karta("4111111111111111"))); // karta kończąca się na 1111
System.out.println(opis(new Blik("123456"))); // BLIK 123456
System.out.println(opis(new Przelew("PL61109010140000071219812874"))); // przelew z PL
}
}
Dla testera to bardzo konkretna korzyść. Gdy programista doda czwartą metodę płatności do permits i zapomni o niej w switch, kod się nie skompiluje. Błąd, który dawniej wychodził w testach albo na produkcji jako nieobsłużony przypadek, wyłapuje kompilator. A Wy dostajecie od razu listę wartości do testów: tyle przypadków, ile typów w permits.
Wbrew pozorom
Najsłynniejszy przykład nadużycia dziedziczenia siedzi w samej bibliotece standardowej. Klasa java.util.Stack z Javy 1.0 dziedziczy po Vector, więc stos pozwala wstawić element do środka metodą add(0, ...), co z definicją stosu nie ma nic wspólnego. Według dokumentacji API Java SE 25 lepiej dziś zamiast niej wybrać interfejs Deque i klasę ArrayDeque. Klasy Stack nie dało się już poprawić, bo kod na całym świecie korzysta z odziedziczonych metod.
StosDemo.java, wynik w komentarzach
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Stack;
public class StosDemo {
public static void main(String[] args) {
Stack<String> stos = new Stack<>();
stos.push("strona 1");
stos.push("strona 2");
stos.add(0, "wstawione do środka"); // Stack dziedziczy po Vector, więc to działa
System.out.println(stos); // [wstawione do środka, strona 1, strona 2]
Deque<String> historia = new ArrayDeque<>();
historia.push("strona 1");
historia.push("strona 2");
System.out.println(historia.pop()); // strona 2
// historia.add(0, "...") się nie skompiluje: Deque nie ma wstawiania po indeksie
}
}
Kiedy kompozycja jest lepsza niż dziedziczenie?
Kompozycja to budowanie klasy z innych obiektów, które trzyma ona w polach, zamiast dziedziczenia po nich. Zasadę „przedkładajcie kompozycję obiektów nad dziedziczenie klas” sformułowała w 1994 roku książka „Design Patterns” Gammy, Helma, Johnsona i Vlissidesa, a Joshua Bloch w „Effective Java” (wydanie 3, 2018, temat 18) pokazuje, jak dziedziczenie po cudzej klasie psuje się przy zmianie jej wnętrza. Test „jest” kontra „ma” rozstrzyga większość przypadków.
Kompozycja.java, wynik w komentarzach
import java.util.ArrayList;
import java.util.List;
class Logger {
private final List<String> wpisy = new ArrayList<>();
void zapisz(String wpis) {
wpisy.add(wpis);
}
List<String> wpisy() {
return List.copyOf(wpisy);
}
}
class KlientApi {
private final Logger logger; // KlientApi MA logger, a nie JEST loggerem
KlientApi(Logger logger) {
this.logger = logger;
}
int pobierzStatus(String sciezka) {
logger.zapisz("GET " + sciezka);
return sciezka.startsWith("/api/") ? 200 : 404;
}
}
public class Kompozycja {
public static void main(String[] args) {
Logger logger = new Logger();
KlientApi klient = new KlientApi(logger);
System.out.println(klient.pobierzStatus("/api/produkty")); // 200
System.out.println(klient.pobierzStatus("/brak")); // 404
System.out.println(logger.wpisy()); // [GET /api/produkty, GET /brak]
}
}
KlientApi ma logger w polu i korzysta z niego, ale nie wystawia na zewnątrz metod loggera. Możecie podmienić logger na inny bez ruszania klienta, a w teście podać logger, który zapisuje wpisy do listy, i sprawdzić je asercją. Gdyby KlientApi dziedziczył po Logger, każdy użytkownik klienta mógłby wołać zapisz bezpośrednio, dokładnie jak z wstawianiem do środka stosu.

Zalety z grafiki, czyli wielokrotne użycie kodu, mniej powtórzeń i czytelniejsza struktura, są prawdziwe, ale mają cenę. Z naszego doświadczenia przy audytach frameworków testów najdroższy w utrzymaniu bywa łańcuch BaseTest, BaseUiTest, BaseLoggedInTest, BaseAdminTest, w którym nikt już nie wie, co dzieje się przed testem. Rozsądny limit to jeden, najwyżej dwa poziomy dziedziczenia, a resztę wspólnego kodu wyciągajcie do klas pomocniczych używanych przez kompozycję. Hierarchia klas głębsza niż drzewo genealogiczne Waszej rodziny to sygnał, że coś poszło nie tak.
Dziedziczenie w testach: BaseTest i BasePage
W automatyzacji testów dziedziczenie spotkacie przede wszystkim w dwóch miejscach. BaseTest zbiera to, co dzieje się przed każdym testem i po nim: start przeglądarki, dane konfiguracyjne, sprzątanie. BasePage zbiera wspólny kod obiektów stron: sterownik, oczekiwania i bezpieczne kliknięcie. Dokumentacja Selenium o wzorcu Page Object opisuje obiekty stron jako jedyne miejsce, które zna strukturę strony, a klasa bazowa pozwala nie powtarzać w nich tego samego kodu.
BasePage.java, Selenium 4
package pl.qi.strony;
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public abstract class BasePage {
protected final WebDriver driver;
private final WebDriverWait wait;
protected BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
protected WebElement widoczny(By lokator) {
return wait.until(ExpectedConditions.visibilityOfElementLocated(lokator));
}
protected void kliknij(By lokator) {
wait.until(ExpectedConditions.elementToBeClickable(lokator)).click();
}
protected void wpisz(By lokator, String tekst) {
WebElement pole = widoczny(lokator);
pole.clear();
pole.sendKeys(tekst);
}
public abstract boolean jestOtwarta();
}
StronaLogowania.java, podklasa BasePage
package pl.qi.strony;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class StronaLogowania extends BasePage {
private static final By LOGIN = By.id("username");
private static final By HASLO = By.id("password");
private static final By ZALOGUJ = By.cssSelector("button[type='submit']");
public StronaLogowania(WebDriver driver) {
super(driver);
}
public void zalogujJako(String login, String haslo) {
wpisz(LOGIN, login);
wpisz(HASLO, haslo);
kliknij(ZALOGUJ);
}
@Override
public boolean jestOtwarta() {
return widoczny(LOGIN).isDisplayed();
}
}
Obie klasy skompilowaliśmy z bibliotekami Selenium 4 (selenium-api i selenium-support). Metoda jestOtwarta jest abstrakcyjna, więc każda nowa strona musi powiedzieć, jak rozpoznać, że się załadowała. Metody pomocnicze są protected: korzystają z nich strony, ale test ich nie widzi i woła tylko czytelne operacje, takie jak zalogujJako.
BaseTest działa podobnie, a JUnit dokłada do tego jedną ważną regułę: metody z adnotacją @BeforeEach z klasy bazowej wykonują się przed metodami @BeforeEach z podklasy, a @AfterEach w odwrotnej kolejności. Sprawdziliśmy to w JUnit 6.1.3 na przykładzie bez przeglądarki, gdzie każdy krok zapisuje się w dzienniku.
BaseTest.java, JUnit 6
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
abstract class BaseTest {
protected final List<String> dziennik = new ArrayList<>();
@BeforeEach
void przygotujSrodowisko() {
dziennik.add("BaseTest: start przeglądarki");
}
@AfterEach
void posprzataj() {
dziennik.add("BaseTest: zamknięcie przeglądarki");
System.out.println(dziennik);
}
}
KoszykTest.java, test dziedziczący po BaseTest
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class KoszykTest extends BaseTest {
@BeforeEach
void zalogujKlienta() {
dziennik.add("KoszykTest: logowanie klienta");
}
@Test
void pustyKoszykMaZeroProduktow() {
dziennik.add("KoszykTest: test");
assertEquals(3, dziennik.size());
}
}
Konsola po uruchomieniu, test zakończony sukcesem
[BaseTest: start przeglądarki, KoszykTest: logowanie klienta, KoszykTest: test, BaseTest: zamknięcie przeglądarki]
Jeśli chcecie zobaczyć, jak takie klasy pracują w całym frameworku, przeczytajcie tekst o wzorcach projektowych w automatyzacji testów Selenium WebDriver. Jak rozplanować wdrożenie automatyzacji w firmie, krok po kroku na pół roku, opisuje Strefa QA w planie wdrożenia automatyzacji testów na 6 miesięcy.
Jakie błędy zgłosi kompilator?
Kompilator Javy pilnuje reguł dziedziczenia bardzo dokładnie. Poniżej sześć błędów, które najczęściej widzimy u kursantów, z dosłownym komunikatem javac z JDK 25. Każdy odtworzyliśmy na osobnym pliku.
Źródło: komunikaty skopiowane z kompilatora javac (OpenJDK, opcja --release 25), sprawdzone przez Quality Island 11.10.2026.
Ostatni komunikat jest mylący, bo nie mówi wprost „Java nie ma wielodziedziczenia”. Parser po prostu nie spodziewa się przecinka po pierwszej nadklasie. Dobrze o tym wiedzieć, zanim spędzicie kwadrans na szukaniu brakującej klamry.
Ćwiczenie z rozwiązaniem
Zadanie: napiszcie klasę abstrakcyjną Konto z polem saldo, abstrakcyjną metodą limitWyplaty() i metodą wyplac(int kwota), która odrzuca kwoty niedodatnie i większe niż limit. Następnie dwie podklasy: KontoOsobiste, gdzie limit to saldo, i KontoZDebetem, gdzie limit to saldo plus debet. Zanim zajrzycie do rozwiązania, wypiszcie wartości brzegowe, które sprawdzicie.
Cwiczenie.java, rozwiązanie z wynikami
abstract class Konto {
protected int saldo;
Konto(int saldo) {
this.saldo = saldo;
}
abstract int limitWyplaty();
boolean wyplac(int kwota) {
if (kwota <= 0 || kwota > limitWyplaty()) {
return false;
}
saldo -= kwota;
return true;
}
}
class KontoOsobiste extends Konto {
KontoOsobiste(int saldo) {
super(saldo);
}
@Override
int limitWyplaty() {
return saldo;
}
}
class KontoZDebetem extends Konto {
private final int debet;
KontoZDebetem(int saldo, int debet) {
super(saldo);
this.debet = debet;
}
@Override
int limitWyplaty() {
return saldo + debet;
}
}
public class Cwiczenie {
public static void main(String[] args) {
Konto osobiste = new KontoOsobiste(1000);
Konto zDebetem = new KontoZDebetem(1000, 500);
System.out.println(osobiste.wyplac(1200)); // false
System.out.println(zDebetem.wyplac(1200)); // true
System.out.println(zDebetem.saldo); // -200
System.out.println(zDebetem.wyplac(301)); // false, limit to 300
System.out.println(osobiste.wyplac(0)); // false
}
}
Na co zwrócić uwagę. Reguła wypłaty mieszka w jednym miejscu, w klasie bazowej, a podklasy dostarczają tylko to, czym się różnią, czyli limit. To wzorzec Template Method w najprostszej postaci. Przypadki testowe w main pokrywają przekroczenie limitu, wypłatę w debecie, przekroczenie o jedną złotówkę po zmianie salda i kwotę zero. Dopiszcie jeszcze wypłatę równą limitowi, która powinna przejść, oraz kwotę ujemną.
Co dalej po tej lekcji
Powtórka
Klasy i obiekty oraz konstruktory: bez nich dziedziczenie nie ma na czym pracować.
Praktyka
Page Object z BasePage na prawdziwej stronie: jedna klasa bazowa, trzy strony, dziesięć testów.
Na szkoleniu
Programowanie obiektowe w Javie z trenerem, z przeglądem Waszego kodu.
To ostatnia z lekcji kursu opublikowanych dotąd na blogu. Jeśli chcecie przećwiczyć dziedziczenie, interfejsy i wzorce obiektowe pod okiem trenera, zobaczcie szkolenie programowanie obiektowe w Javie. Gdy celem są od razu testy, lepszym wyborem będzie programowanie w Javie dla testerów albo szkolenie z automatyzacji testów w Selenium, a gdy potrzebujecie gotowego frameworka z BasePage i BaseTest zbudowanego przez zespół z doświadczeniem, zakres pokazuje strona automatyzacji testów UI. Przez szkolenia Quality Island przeszło już ponad 2 500 specjalistów.
Co zabrać z tego artykułu
01Dziedziczenie w Javie to extends: podklasa przejmuje pola i metody rodzica. Pytanie kontrolne: czy B jest rodzajem A?
02super(…) wywołuje konstruktor klasy bazowej i musi stać w pierwszej linii konstruktora podklasy.
03@Override piszcie zawsze. Bez niej literówka tworzy nową metodę, a test sprawdza nie to, co myślicie.
04Klasa abstrakcyjna, gdy podklasy dzielą stan. Interfejs, gdy różne klasy mają umieć to samo. Sealed, gdy lista podtypów jest zamknięta.
05W testach BaseTest i BasePage, ale najwyżej dwa poziomy. Resztę wspólnego kodu dawajcie przez kompozycję.
Wasz framework testów ma pięć poziomów klas bazowych i nikt nie wie, co dzieje się przed testem? Uporządkujemy go albo zbudujemy od nowa z BasePage i kompozycją.
Powiązane na blogu Quality Island
- Wprowadzenie do języka Java i spis lekcji kursu
- 14. Programowanie obiektowe: klasy i obiekty
- 15. Konstruktory w Javie
- 17. Hermetyzacja w Javie
- Java dla testerów automatyzujących: plan nauki
Pełna lista źródeł
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 8: Classes, 2025. Rozdziały 8.1.4 (nadklasa), 8.4.8 (dziedziczenie i nadpisywanie), 8.8.7 (wywołanie konstruktora nadklasy).
- Oracle, The Java Tutorials: Inheritance, stan na 10.10.2026.
- Oracle, The Java Tutorials: Overriding and Hiding Methods, stan na 10.10.2026.
- Oracle, The Java Tutorials: Abstract Methods and Classes, stan na 10.10.2026.
- Oracle, Java SE 25 API: adnotacja Override, 2025.
- Oracle, Java SE 25 API: klasa Stack, 2025. Stąd zalecenie Deque zamiast Stack.
- OpenJDK, JEP 409: Sealed Classes, 2021, Java 17.
- OpenJDK, JEP 441: Pattern Matching for switch, 2023, Java 21.
- Selenium, Page object models, dokumentacja Selenium, 2026.
- JUnit, Test Classes and Methods, dziedziczenie metod cyklu życia, 2026.
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, „Design Patterns: Elements of Reusable Object-Oriented Software”, Addison-Wesley, 1994.
- Joshua Bloch, „Effective Java”, wydanie 3, Addison-Wesley, 2018, temat 18: Favor composition over inheritance.
- Dane własne Quality Island, 2026: ponad 450 000 napisanych testów automatycznych, ponad 2 500 przeszkolonych specjalistów.



