Poruszone zagadnienia
- Konstruktory
- Rodzaje konstruktorów
- Konstruktor domyślny
- Przeciążenie konstruktorów
- Constructor chaining
Wstęp
Dziś pobawimy się trochę w inżynierów budownictwa. Nie będziemy projektować mostów, wieżowców, czy innych budowli. Skupimy się na stawianiu konstrukcji o zupełnie innym charakterze, a mianowicie na na schemacie tworzenia
obiektów
w Javie.Nie musimy się martwić o przepisy budowlane, ani o materiały. Wszystko zadzieje się w magiczny sposób. Jedyne dyrektywy, jakie będziemy potrzebowali zgłębić, to zasady tworzenia tzn.
konstruktorów
, czyli tworów podobnych do metod w Javie
, których znajomość musimy opanować , aby jakiś doświadczony inżynier oprogramowania
nie oskarżył nas o fuszerkę podczas budowania naszych obiektów
.Konstruktory
stanowią kluczowy element programowania obiektowego
, umożliwiając nam tworzenie instancji klas, czyli obiektów
, zbudowanych na podstawie wcześniej zdefiniowanych szablonów - klas
.W dzisiejszym wpisie zgłębimy tajniki tego ważnego zagadnienia i poznamy różne aspekty pracy z
konstruktorami
w Javie
. Dowiemy się, jakie są ich rodzaje konstruktorów
, jak je tworzyć, czym się różnią od metod
oraz jakie mogą mieć zastosowanie w naszych projektach.Zanim jednak wkroczymy na plac budowy, spójrzmy na podstawy teorii
konstruktorów
, co pozwoli nam na wylać beton pod solidne fundamenty.¡Vamos! 🇪🇸
Obiekty
Najpierw przypomnimy sobie podstawy projektowania obiektowego. Jeśli potrzebujesz sobie powtórzyć ten temat, opisałem to zagadnienie tutaj: javamPokaze || Podstawy programowania obiektowego
Zasada tworzenia obiektów jest prosta:
public class Cat { String name; } public class TestCat{ public static void main(String [] args){ Cat kitty = new Cat(); // Tworzymy nowy obiekt typu Cat //Etapy: 1 3 2 kitty.name = "Tom"; } }
Etap 1. Deklaracja
Prosimy wirtualną maszynę Javy (
JVM
), aby zarezerowała dla nas miejsce w pamieci (na tzw. Stosie
) dla zmiennej referencyjnej typu Cat
o nazwie kitty
.Etap 2. Tworzenie Obiektu
Wnioskujemy u
JVM
o rezerwacje miejsca na tzw. Stercie
dla nowego obiektu
, który tworzymy poprzez słowo kluczowe new
Etap 3. Inicjalizacja (przypisanie wartości)
Wykorzystując znak równości przypisujemy nowy
obiekt
do zmiennej referencyjnej
o nazwie kitty
Ok, stworzyliśmy nowego kotka. Nadaliśmy mu imię Tom. Ale czy zastanawiałeś się kiedykolwiek, jaki mechanizm stoi za powyższą operacją?
Konstruktory
Najpierw rzućmy okiem na ten sam kod, jednak z pewnymi zmianami:
public class Cat { String name; public Cat(){ // to jest konstruktor // miejsce na kod } } public class TestCat{ public static void main(String [] args){ Cat kitty = new Cat(); // wywołanie kontruktora poprzez użycie słowa kluczowego "new" kitty.name = "Tom"; } }
Jak pewnie zauważyłeś w klasie
Cat
pojawił się nowy fragment kodu public Cat()
, który wyglada jak metoda
. To jest właśnie ten słynny konstruktor
.Konstruktor
to specjalny blok kodu, który służy do tworzenia obiektów. Jest bardzo podobny jest do metody
, z tą różnicą, że metoda
w Javie posiada typ zwracany, a konstruktor
nie. Mechanizm, który pozwala tworzyć KAŻDY obiekt w Javie nazywa się wywoływaniem konstruktów
Konstruktor
znajduje się zawsze w klasie, której instancję (obiekt)
tworzy. Kiedy chcesz stworzyć dany obiekt, wykorzystujesz wspomniane wywołanie konstruktora
. Wywołanie następuje w momencie, kiedy użyjesz słowa kluczowego new
(w naszym przypadku new Cat()
. Wirtulana maszyna Javy znajduje wtedy klasę, w której konstruktor
się znajduje i wywołuje go (tak jak zwykłą metodę
) wykonując kod w nim zawarty.W dużym skrócie - jeśli tworzysz nowy obiekt, to zawsze automatycznie wywołujesz konstruktor, czyli narzędzie służące do kreowania tego obiektu. Nawet jeśli o tym nie wiesz, to nieświadomie go używasz (ale o tym zaraz)😜
WAŻNE:
⚠️ Konstruktor nie jest metodą (nie posiada typu zwracanego)
⚠️ Oznaczenie konstruktora jako public
jest najbardziej liberalnym podejściem, ponieważ umożliwia dostęp do konstruktora z dowolnej klasy, również spoza pakietu
⚠️ Nazwa konstruktora musi być identyczna z nazwą klasy
Rodzaje konstruktorów / jak je tworzyć?
Główą funkcją
konstruktora
jest utworzenie obiektu
oraz sprawne zainicjowanie zmiennych instancji
. Bez niego nie stworzymy żadnego obiektu
. Wyróżniamy dwa rodzaje konstruktorów:- Konstruktory bezparametrowe (domyślne)
- Konstruktory parametrowe
🏗️ Konstruktor bezparametrowy (domyślny)
- To pierwszy rodzaj konstruktora
- Nie przyjmuje parametrów
- Przypisuje wartości domyślne zmiannym instancji tworzonego obiektu
public class Car{ String brand; int price; public Car(){ // Puste ciało } // Wywołanie konstruktora: // Spowoduje przypisanie zmiennym instancji wartości domyślnych: // int → 0, boolean → false, float → 0.0, String = null }
Konstruktor z ciałem
public class Car{ String brand; int price; public Car(){ brand = "Ford"; // ustawione wartości domyślne w ciele konstruktora price = 20000; } // Wywołanie konstruktora: // Spowoduje, że powyższe wartości będą przypisane do // zmiennych instancji każdego obiektu, który stworzymy w przyszłości // każdy obiekt będzie Fordem w cenie 20000 PLN }
🏗️ Konstruktor z parametrami
- To drugi rodzaj konstruktora
- Przyjmuje konkretne parametry
- Wartości przekazane do parametrów konstruktora, jako agrumenty będą przypisane do zmiennych instanacji obiektu, który tworzymy
- Rozważmy dwa przypadki:
Zmianne instancji oraz parametry konstruktora różnią się nazwą
Zmianne instancji oraz parametry konstruktora różnią się nazwą
public class Car{ String brand; int price; public Car(String marka, int cena ){ brand = marka; // przypisanie wartości marka do zmiennej instancji brand price = cena ; // przypisanie wartości cena do zmiennej instancji price } } public CarTest{ public static void main(String [] args){ Car combi = new Car("Audi", 160000); // Wywołaliśmy konstruktor poprzez słowo kluczowe "new" // Jako argumenty przekazaliśmy do konstruktora wartości "Audi" oraz 160000 // Konstruktor przypisał zmiennym instancji te wartości: // brand = "Audi"; // price = 160000; // W tym przypadku rozróżniamy parametry konstruktora od zmiennych instancji po nazwie } }
Zmianne instancji
oraz
parametry konstruktora
mają
takie same nazwy
public class Car{ String brand; int prices; public Car(String brand, int price){ // takie same nazwy (brand / brand) this.brand = brand; this.price = price; } // W tym przypadku rozróżniamy parametry konstruktora od zmiennych instancji poprzez: // this.brand = brand // Możemy to przetłumaczyć na coś takiego: /* Hej kompilator, weź tą "this." zmienną instancji brand w klasie Car i przypisz jej wartość brand, którą przekazuję jako argumenet podczas wywoływania konstruktora w metodzie public static void main ("Audi") */ } public CarTest{ public static void main(String [] args){ Car combi = new Car("Audi", 160000); // Wywołaliśmy konstruktor, do którego przekazaliśmy wartości "Audi" oraz 160000 // Konstruktor przypisze te wartości zmiennym instancji obiektu poprzez słowo kluczowe: // "this." // this.brand = "Audi"; // this.price = 160000; } }
WAŻNE:
⚠️ Każda klasa posiada konstruktor
, nawet jeśli Ty sam go w tej klasie nie umieścisz. Jeśli nie stworzysz swojego konstruktora, kompilator z automatu sam stworzy w klasie “niewidzialny” konstruktor domyślny
. Dowodem na to jest fakt, że nie ma możliwości stworzenia obiektu
bez parametrów. Zostaną mu automatycznie przypisane wartości domyślne (0, 0.0, false, lub null)
⚠️ Jeśli stworzysz konstruktor, który przyjmuje argumenty, kompilator odpuści sobie utworzenie konstruktora domyślnego
, bo stwierdzi, że go nie potrzebujesz
⚠️ Dlatego jeśli potrzebujesz zarówno konstruktora z argumentami, jaki i bez argumentów, to ten drugi będziesz musiał stworzyć dodatkowo sam
Przeciążenie konstruktorów
W każdej klasie możemy mieć zarówno jeden, jak i kilka
konstruktorów
. Aby utworzyć kilka różnych konstruktorów, muszą one różnić się parametrami (ilością, typami, lub kolejnością w nawiasie). Aby mieć wiele konstruktorów z różnymi listami parametrów, musisz skorzystać z techniki przeciążania konstruktorów
(Overload
).Przeciążanie konstruktorów
umożliwia tworzenie wielu konstruktorów z różną konfiguracją parametrów, co zapewnia elastyczność podczas tworzenia obiektów danej klasy. Dzięki temu programista może łatwo tworzyć obiekty tej klasy, używając konstruktora odpowiadającego konkretnym potrzebom. Jest to swego rodzaju metoda na tworzenie obiektów szytych na miarę. Dostosowujemy stan obiektu do naszych potrzeb.Więcej na temat
przeciążenia
znajdziesz w moim wcześniejszym wpisie:Weźmy w takim razie na bęben taki przykład:
public class Smartphone{ String brand; int price; int megaPixels; // Konstruktor domyślny (tworzy obiekt i przypisuje zmiennymi instacji wartości // domyślne (brand = null, price = 0, megaPixels = 0) class Smartphone(){ } // Konstruktor z 3 parametrami (które przekażemy podczas wywoływania konstruktora) class Smartphone(String brand, int price, int megaPixels){ this.brand = brand; this.price = price; this.megaPixels = megaPixels } // Konstruktor z 2 parametrami (które przekażemy podczas wywoływania konstruktora) class Smartphone(String brand, int price){ this.brand = brand; this.price = price; } // Ten sam Konstruktor z 2 parametrami co wyżej, ale z inną kolejnością zmiennych w // nawiasie class Smartphone(int price, String brand){ this.price = price; this.brand = brand; } // Konstruktor 1 parametrem (które przekażemy podczas wywoływania konstruktora) class Smartphone(String brand){ this.brand = brand; } // Konstruktor 2 parametrami i 1 bezpośrednio przypisaną wartością domyślną "iPhone" class Smartphone(int price, int megaPixels){ String = "iPhone" this.price = price; this.megaPixels = megaPixels } }
Stwórzmy zatem
obiekty
, wywołując odpowiednie konstruktory.
Zastanawiasz się pewnie, jak kompilator
ma się zorientować, który konkretnie konstruktor planujesz wywołać? Dowie się tego po tym, jaką ilość argumentów i w jakiej kolejności przekażesz do konstruktora.Dodatkowym atutem konstruktorów jest to, że możemy zainicjować wszystkie zmienne instancji w 1 linijce kodu, ponieważ wszystkie wartości zostaną przekazane na raz (w nawiasie konstruktora)
public class TestSmartphone{ public static void main(String [] args){ // wywołanie konstruktora domyślnego (zmienne mają wartości domyślne) Smartphone phone = new Smartphone(); // wywołanie konstruktora z 3 parametrami (zmiennym przypisujemy nasze wartości) Smartphone phone = new Smartphone("LG", 1500, 10); // wywołanie konstruktora z 2 parametrami (zmiennym przypisujemy nasze wartości) Smartphone phone = new Smartphone("LG", 2500); // wywołanie konstruktora z 2 parametrami (zmiennym przypisujemy nasze wartości) Smartphone phone = new Smartphone(2500, "LG"); // wywołanie konstruktora z 1 parametrem (zmiennej przypisujemy naszą wartość) Smartphone phone = new Smartphone("Xiaomi"); // wywołanie konstruktora z 2 parametrami (zmiennym przypisujemy tylko 2 wartości // ponieważ zmienna brand została w konstrukorze zainicjowana jako brand = "iPhone") Smartphone phone = new Smartphone("5000", 20); } }
Ważne:
⚠️ Kiedy tworzysz swoje własne konstruktory
, oprócz nich staraj się umieszczać także konstruktor domyślny
. Może to ułatwić przyszłą ewolucję kodu
⚠️ Aby mieć kilka konstruktorów
o tej samej nazwie w jednej klasie, należy zastosować przeciążanie konstruktorów
, czyli definiować konstruktory z różnymi listami parametrów (ilością, typami lub kolejnością)
Wywołanie konstruktora z innego konstuktora
No teraz się zacznie robić ciekawie 😄 Tak, możemy wywołać jeden konstruktor z drugiego, a drugi z trzeciego, trzeci z czwartego… Miło, nie? 🤯
Wywoływanie
konstruktora
przy pomocy słowa kluczowego this
jest techniką nazywaną "konstruktorem łańcuchowym" (ang. constructor chaining
). Pozwala ona na uniknięcie powtarzania kodu w kolejnych konstruktorach, poprzez przekierowanie wywołania do innego konstruktora.Kiedy w danym konstruktorze użyjemy słowo kluczowe
this(...)
z odpowiednimi argumentami, oznacza to, że chcemy wywołać inny konstruktor, w którym mamy możliwość niektóre ustawić paramametry wedle naszych upodobań, a resztę pozostawiamy w spokoju. Wywołując konstruktor w innym konstruktorze, unikamy konieczności powtarzania podobnego kodu kilka razy. To oszczędzić czasu.Popatrzmy na ten przykład:
class Car{ //Nasze zmienne instancji String brand; int productionAge; double przebieg; // Konstruktor domyślny (bezargumentowy) wywołuje konstruktor Car(String brand) // z 1 argumentem. Tutaj zmiennej model przypisujemy wartość domyślną "BMW" : public Car(){ this("BMW"); } // Konstruktor z 1 argumentem wywołuje konstruktor Car(String brand, int productionAge) // z 2 argumentami. Tutaj zmiennej int przypisujemy wartość domyślną 2005 : public Car(String brand){ this(brand, 2005) } // Konstruktor z 2 argumentami wywołuje Car(String brand, int productionAge, double przebieg) // z 3 argumentami. Tutaj zmiennej double przypisujemy wartość domyślną 200.000 : public Car(String brand, int productionAge){ this(brand, productionAge, 200.000); } // Konstruktor z 3 argumentami, w którym ustawiamy wszystkie wartości // Od niego zaczynamy zabawę public Car(String brand, int productionAge, double przebieg){ this.brand = brand; this.productionAge = productionAge; this.przebieg = przebieg; } }
Po co to robić? Przykładowo, jeśli potrzebujemy stworzyć kilka podobnych samochodów klasy
Car
, które łączy jedna wspólna cecha (np. kilka samochodów o różnych markach, różnym roku produkcji ,ALE TAKIM SAMYM PRZEBIEGU). Wtedy takie rozwiązanie ma sens.public CarTest{ public static void main(String [] args){ // Tworzymy pierwszy obiekt wywołując konstruktor z 3 parametrami // Ustawimy zmiennym instancji konkretne wartości przekazując je jako argumenty Car car1 = new Car("BMW", 2005, 54000); // Tworzymy 2 obiekty wywołując konstruktor z 2 parametrami // W tym konstruktorze wywołany został konstruktor z 3 parametrami // następnie zmieniamy JEDYNIE markę i rok produkcji // przebieg zostaje taki sam (54000) Car car2 = new Car("Audi", 2010); Car car2 = new Car("Toyota", 1998); } }
Słownik
- Konstruktor - specjalny blok kodu, służący do tworzenia obiektów. Jest bardzo podobny do metody, z tą różnicą, że metoda posiada typ zwracany, a konstruktor nie. Główną funkcją konstruktora jest tworzenie obiektó oraz sprawne zainicjowanie zmiennych instancji. Mechanizm przy pomocy którego powstają obiekty nazywa się wywoływaniem konstruktorów.
- Konstruktory bezparametrowe (domyślne) - przypisują zmiennym instancji wartości domyślne. Wartości te możemy ustawić sami w ciele konstruktora. Jeśli tego nie zrobimy, kompilator sam przypisze zmiennym instancji wartości domyślne (0, 0.0, null lub false, w zależności od typu zmiennych)
- Konstruktory parametrowe - zmiennym istancji zostaną przypisane wartości przekazane przez nas jako argumenty do konstruktora
- Przeciążenie konstruktorów - koncepcja umożliwiajaca tworzenie jednocześnie wielu konstruktorów w tej samej klasie. Konstuktory te muszą różnić się konfiguracją parametrów. Przeciążenie konstruktorów zapewnia elastyczność podczas późniejszego tworzenia obiektów. Aby utworzyć kilka różnych konstruktorów, muszą one różnić się parametrami (ilością, typami, lub kolejnością w nawiasie).
- Constructor chaining - technika pozwalająca na wywołanie konstruktorów w innych konstruktorach przy użyciu słowa kluczowego “this.” . Celem takiego działania jest uniknięcie powtarzania kodu, jeśli w nowym konstruktorze potrzebujemy zapożyczyć funkcjonalności z poprzedniego konstruktora.
Przydatne linki
Śledź mnie na LinkedIn
Newsletter
Jeśli masz jakieś sugestie lub pytania, proszę napisz do mnie wiadomość: kuba@javampokaze.pl