Cześć! Dziś przyjrzymy się bardzo ważnemu tematowi, który dotyczy naszych obiektów. Tutaj bez przesady możemy powiedzieć, że tę wiedzę będziesz wykorzystywać na co dzień w prawdziwej pracy! Porozmawiamy o konstruktorach. Być może słyszysz to określenie po raz pierwszy, ale tak naprawdę prawdopodobnie korzystałeś z konstruktorów, ale sam tego nie zauważyłeś :) Zobaczymy to później.
Czy pamiętasz, jak na początku wykładu mówiliśmy, że korzystałeś już z konstruktorów, ale po prostu tego nie zauważyłeś? To prawda. Faktem jest, że każda klasa w Javie posiada tzw. konstruktora domyślnego. Nie ma żadnych argumentów, ale uruchamia się za każdym razem, gdy tworzony jest dowolny obiekt dowolnej klasy.
Co to jest konstruktor w Javie i dlaczego jest potrzebny?
Spójrzmy na dwa przykłady.public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car bugatti = new Car();
bugatti.model = "Bugatti Veyron";
bugatti.maxSpeed = 407;
}
}
Stworzyliśmy nasz samochód i ustaliliśmy jego model oraz prędkość maksymalną. Jednak w prawdziwym projekcie obiekt Car będzie miał wyraźnie więcej niż 2 pola. I na przykład 16 pól!
public class Car {
String model;//Model
int maxSpeed;//maksymalna prędkość
int wheels;// szerokość dysku
double engineVolume;//pojemność silnika
String color;//kolor
int yearOfIssue;//rok wystawienia
String ownerFirstName;//Imię właściciela
String ownerLastName;//nazwisko właściciela
long price;//cena
boolean isNew;//nowy czy nie
int placesInTheSalon;//liczba miejsc w kabinie
String salonMaterial;// materiał wnętrza
boolean insurance;//czy jest ubezpieczony
String manufacturerCountry;//kraj producenta
int trunkVolume;// objętość bagażnika
int accelerationTo100km;//przyspieszenie do 100 km/h w sekundach
public static void main(String[] args) {
Car bugatti = new Car();
bugatti.color = "blue";
bugatti.accelerationTo100km = 3;
bugatti.engineVolume = 6.3;
bugatti.manufacturerCountry = "Italy";
bugatti.ownerFirstName = "Amigo";
bugatti.yearOfIssue = 2016;
bugatti.insurance = true;
bugatti.price = 2000000;
bugatti.isNew = false;
bugatti.placesInTheSalon = 2;
bugatti.maxSpeed = 407;
bugatti.model = "Bugatti Veyron";
}
}
Stworzyliśmy nowy obiekt Car . Jeden problem: mamy 16 pól, ale zainicjowaliśmy tylko 12 ! Spróbuj teraz użyć kodu, aby znaleźć te, o których zapomnieliśmy! Nie tak łatwo, prawda? W takiej sytuacji programista może łatwo popełnić błąd i pominąć inicjalizację jakiegoś pola. W rezultacie zachowanie programu stanie się błędne:
public class Car {
String model;//Model
int maxSpeed;//maksymalna prędkość
int wheels;// szerokość dysku
double engineVolume;//pojemność silnika
String color;//kolor
int yearOfIssue;//rok wystawienia
String ownerFirstName;//Imię właściciela
String ownerLastName;//nazwisko właściciela
long price;//cena
boolean isNew;//nowy czy nie
int placesInTheSalon;//liczba miejsc w kabinie
String salonMaterial;// materiał wnętrza
boolean insurance;//czy jest ubezpieczony
String manufacturerCountry;//kraj producenta
int trunkVolume;// objętość bagażnika
int accelerationTo100km;//przyspieszenie do 100 km/h w sekundach
public static void main(String[] args) {
Car bugatti = new Car();
bugatti.color = "blue";
bugatti.accelerationTo100km = 3;
bugatti.engineVolume = 6.3;
bugatti.manufacturerCountry = "Italy";
bugatti.ownerFirstName = "Amigo";
bugatti.yearOfIssue = 2016;
bugatti.insurance = true;
bugatti.price = 2000000;
bugatti.isNew = false;
bugatti.placesInTheSalon = 2;
bugatti.maxSpeed = 407;
bugatti.model = "Bugatti Veyron";
System.out.println(„Model Bugatti Veyron. Rozmiar silnika -” + bugatti.engineVolume + ", pień - " + bugatti.trunkVolume + "salon składa się z" + bugatti.salonMaterial +
", szerokość dysku - " + bugatti.wheels + ". Został przejęty w 2018 roku przez pana " + bugatti.ownerLastName);
}
}
Wyjście konsoli:
Model Bugatti Veyron. Poj. silnika - 6,3, bagażnik - 0, wnętrze null, szerokość felgi - 0. Został zakupiony w 2018 roku przez pana nulla
Twojemu nabywcy, który zapłacił za samochód 2 miliony dolarów, najwyraźniej nie będzie się podobać, gdy nazywa się go „panem Nullem”! Ale tak na poważnie, w efekcie nasz program skończył z niepoprawnie utworzonym obiektem - samochodem z felgą o szerokości 0 (czyli w ogóle bez felg), brakującym bagażnikiem, wnętrzem wykonanym z nieznanego materiału, a nawet należącym do kogoś nieznanego . Można sobie tylko wyobrazić, jak taki błąd mógł wystąpić podczas działania programu! Musimy jakoś unikać takich sytuacji. Potrzebujemy, aby nasz program miał ograniczenie: np. podczas tworzenia nowego obiektu pojazdu należy zawsze określić dla niego model i maksymalną prędkość. W przeciwnym razie nie zezwalaj na tworzenie obiektów. Funkcje konstruktora z łatwością radzą sobie z tym zadaniem. Nie bez powodu otrzymali swoją nazwę. Konstruktor tworzy swego rodzaju „szkielet” klasy, któremu musi odpowiadać każdy nowy obiekt klasy. Dla wygody wróćmy do prostszej wersji klasy Car z dwoma polami. Biorąc pod uwagę nasze wymagania, konstruktor klasy Car będzie wyglądał następująco:
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
Tworzenie obiektu wygląda teraz tak:
public static void main(String[] args) {
Car bugatti = new Car("Bugatti Veyron", 407);
}
Zwróć uwagęjak tworzony jest konstruktor. Jest podobna do zwykłej metody, ale nie ma typu zwracanego. W tym przypadku nazwa klasy jest podana w konstruktorze, również wielką literą. W naszym przypadku - Samochód . Ponadto konstruktor używa słowa kluczowego new-to-you this . „to” w języku angielskim oznacza „to, to”. To słowo odnosi się do konkretnego przedmiotu. Kod w konstruktorze:
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
można przetłumaczyć niemal dosłownie: „ model dla tej maszyny (którą właśnie tworzymy) = argument modelu , który jest podany w konstruktorze. maxSpeed dla tej maszyny (którą tworzymy) = argument maxSpeed , który jest określony w konstruktorze.” To jest to, co się stało:
public class Car {
String model;
int maxSpeed;
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
public static void main(String[] args) {
Car bugatti = new Car("Bugatti Veyron", 407);
System.out.println(bugatti.model);
System.out.println(bugatti.maxSpeed);
}
}
Wyjście konsoli:
Bugatti Veyron 407
Konstruktor pomyślnie przypisał wymagane wartości. Być może zauważyłeś, że konstruktor jest bardzo podobny do zwykłej metody! A tak to jest: konstruktor to metoda, tylko trochę specyficzna :) Podobnie jak w metodzie, przekazaliśmy parametry naszemu konstruktorowi. I podobnie jak wywołanie metody, wywołanie konstruktora nie zadziała, jeśli go nie określisz:
public class Car {
String model;
int maxSpeed;
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
public static void main(String[] args) {
Car bugatti = new Car(); //błąd!
}
}
Widzisz, projektant zrobił to, co chcieliśmy osiągnąć. Teraz nie możesz stworzyć samochodu bez prędkości i bez modelu! Na tym nie kończą się podobieństwa między konstruktorami i metodami. Podobnie jak metody, konstruktory mogą być przeciążane. Wyobraź sobie, że masz w domu 2 koty. Jednego wziąłeś jako kociaka, drugiego przyniosłeś z ulicy jako dorosły i nie wiesz dokładnie, ile ma lat. Oznacza to, że nasz program powinien móc tworzyć koty dwóch typów - z imieniem i wiekiem dla pierwszego kota i tylko z imieniem dla drugiego kota. W tym celu przeciążamy konstruktor:
public class Cat {
String name;
int age;
//dla pierwszego kota
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
//dla drugiego kota
public Cat(String name) {
this.name = name;
}
public static void main(String[] args) {
Cat barsik = new Cat("Barsik", 5);
Cat streetCatNamedBob = new Cat("Bob");
}
}
Do pierwotnego konstruktora z parametrami „nazwa” i „wiek” dodaliśmy kolejny, już tylko z nazwą. W ten sam sposób przeciążaliśmy metody na poprzednich lekcjach. Teraz z powodzeniem możemy stworzyć obie wersje kotów :) ![Dlaczego konstruktorzy są potrzebni? - 2](https://cdn.javarush.com/images/article/1bbbe961-5852-43a2-8aab-8cc0754840b7/1024.jpeg)
public class Cat {
public static void main(String[] args) {
Cat barsik = new Cat(); //tutaj działał domyślny konstruktor
}
}
Na pierwszy rzut oka nie jest to zauważalne. No cóż, stworzyliśmy obiekt i stworzyliśmy, gdzie jest praca projektanta? Aby to zobaczyć napiszmy własnymi rękami pusty konstruktor dla klasy Cat , a wewnątrz niego wypiszemy na konsolę jakąś frazę. Jeśli jest wyświetlony, oznacza to, że konstruktor zadziałał.
public class Cat {
public Cat() {
System.out.println(„Stworzył kota!”);
}
public static void main(String[] args) {
Cat barsik = new Cat(); //tutaj działał domyślny konstruktor
}
}
Wyjście konsoli:
Stworzyli kota!
Oto potwierdzenie! Domyślny konstruktor jest zawsze niewidoczny w twoich klasach. Ale musisz poznać jeszcze jedną jego cechę. Domyślny konstruktor znika z klasy, gdy utworzysz konstruktor z argumentami. Dowód na to widzieliśmy już powyżej. Tutaj w tym kodzie:
public class Cat {
String name;
int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Cat barsik = new Cat(); //błąd!
}
}
Nie mogliśmy stworzyć kota bez imienia i wieku, ponieważ zdefiniowaliśmy konstruktor dla Cat : string + number. Domyślny konstruktor zniknął z klasy natychmiast po tym. Dlatego pamiętaj: jeśli potrzebujesz w swojej klasie kilku konstruktorów, w tym pustego, musisz go utworzyć osobno. Przykładowo tworzymy program dla kliniki weterynaryjnej. Nasza klinika pragnie czynić dobre uczynki i pomagać bezdomnym kotom, o których nie znamy ani imienia, ani wieku. Wtedy nasz kod powinien wyglądać tak:
public class Cat {
String name;
int age;
//dla kotów domowych
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
//dla kotów ulicznych
public Cat() {
}
public static void main(String[] args) {
Cat barsik = new Cat("Barsik", 5);
Cat streetCat = new Cat();
}
}
Teraz, gdy już wyraźnie napisaliśmy domyślny konstruktor, możemy stworzyć koty obu typów :) Dla konstruktora (jak dla każdej metody) kolejność argumentów jest bardzo ważna. Zamieńmy argumenty name i age w naszym konstruktorze.
public class Cat {
String name;
int age;
public Cat(int age, String name) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Cat barsik = new Cat("Barsik", 10); //błąd!
}
}
Błąd! Konstruktor wyraźnie stwierdza, że podczas tworzenia obiektu Cat należy przekazać mu liczbę i ciąg znaków w tej kolejności. Dlatego nasz kod nie działa. Pamiętaj o tym i miej to na uwadze tworząc własne zajęcia:
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public Cat(int age, String name) {
this.age = age;
this.name = name;
}
To dwaj zupełnie różni projektanci! Jeśli wyrazimy odpowiedź na pytanie „Po co nam konstruktor?” jednym zdaniem, możemy powiedzieć: tak, aby obiekty były zawsze w odpowiednim stanie. Kiedy użyjesz konstruktorów, wszystkie zmienne zostaną poprawnie zainicjalizowane, a w programie nie będzie żadnych samochodów o prędkości 0 ani innych „nieprawidłowych” obiektów. Ich zastosowanie jest bardzo korzystne przede wszystkim dla samego programisty. Jeśli samodzielnie inicjujesz pola, istnieje duże ryzyko, że coś przeoczysz i popełnisz błąd. Ale tak się nie stanie w przypadku konstruktora: jeśli nie przekazałeś do niego wszystkich wymaganych argumentów lub pomieszałeś ich typy, kompilator natychmiast zgłosi błąd. Warto osobno wspomnieć, że nie należy umieszczać logiki swojego programu w konstruktorze. Aby to zrobić, masz do dyspozycji metody, w których możesz opisać całą potrzebną funkcjonalność. Przyjrzyjmy się, dlaczego logika konstruktora to zły pomysł:
public class CarFactory {
String name;
int age;
int carsCount;
public CarFactory(String name, int age, int carsCount) {
this.name = name;
this.age = age;
this.carsCount = carsCount;
System.out.println(„Nazywa się nasza fabryka samochodów” + this.name);
System.out.println(„Została założona” + this.age + " Lata temu" );
System.out.println(„W tym czasie został wyprodukowany” + this.carsCount + "samochody");
System.out.println(„Średnio produkuje” + (this.carsCount/this.age) + „samochodów rocznie”);
}
public static void main(String[] args) {
CarFactory ford = new CarFactory("Ford", 115 , 50000000);
}
}
Mamy klasę CarFactory opisującą fabrykę produkującą samochody. Wewnątrz konstruktora inicjujemy wszystkie pola i tutaj umieszczamy logikę: wyświetlamy na konsoli informacje o fabryce. Wydawałoby się, że nie ma w tym nic złego, program działał idealnie. Wyjście konsoli:
Nasza fabryka samochodów nazywa się Ford. Powstała 115 lat temu. W tym czasie wyprodukowała 50 000 000 samochodów. Średnio produkuje 434 782 samochody rocznie.
Ale tak naprawdę podłożyliśmy bombę zegarową. A taki kod może bardzo łatwo doprowadzić do błędów. Wyobraźmy sobie, że teraz nie mówimy o Fordzie, ale o nowej fabryce „Amigo Motors”, która istnieje niecały rok i wyprodukowała 1000 samochodów:
public class CarFactory {
String name;
int age;
int carsCount;
public CarFactory(String name, int age, int carsCount) {
this.name = name;
this.age = age;
this.carsCount = carsCount;
System.out.println(„Nazywa się nasza fabryka samochodów” + this.name);
System.out.println(„Została założona” + this.age + " Lata temu" );
System.out.println(„W tym czasie został wyprodukowany” + this.carsCount + "samochody");
System.out.println(„Średnio produkuje” + (this.carsCount/this.age) + „samochodów rocznie”);
}
public static void main(String[] args) {
CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
}
}
Wyjście konsoli:
Nasza fabryka samochodów nazywa się Amigo Motors Wyjątek w wątku „main” java.lang.ArithmeticException: / by zero Została założona 0 lat temu. W tym czasie wyprodukowała 1000 samochodów w CarFactory.<init>(CarFactory.java:15) o godz. CarFactory.main(CarFactory.java:23) Proces zakończony kodem zakończenia 1</init>
Przybyliśmy! Program zakończył się jakimś dziwnym błędem. Spróbujesz zgadnąć, jaki jest tego powód? Powodem jest logika, którą umieściliśmy w konstruktorze. Konkretnie w tej linijce:
System.out.println(„Średnio produkuje” + (this.carsCount/this.age) + „samochodów rocznie”);
Tutaj wykonujemy obliczenia i dzielimy liczbę wyprodukowanych samochodów przez wiek fabryki. A ponieważ nasza fabryka jest nowa (czyli ma 0 lat), wynikiem jest dzielenie przez 0, co jest zabronione w matematyce. W rezultacie program kończy się z błędem. Co powinniśmy byli zrobić? Przenieś całą logikę do osobnej metody i wywołaj ją, na przykład printFactoryInfo() . Możesz przekazać mu obiekt CarFactory jako parametr . Można tam też umieścić całą logikę, a jednocześnie przetworzyć ewentualne błędy, jak nasz z zerowymi latami. Do każdej jego własności. Konstruktory są potrzebne do prawidłowego ustawienia stanu obiektu. Dla logiki biznesowej mamy metody. Nie należy mieszać jednego z drugim.
GO TO FULL VERSION