JavaRush /Java блог /Random UA /Конструктори в Java

Конструктори в Java

Стаття з групи Random UA
Вітання! Сьогодні ми розберемо дуже важливу тему щодо наших об'єктів. Тут без перебільшення можна сказати, що цими знаннями ти користуватимешся щодня у реальній роботі! Ми поговоримо про конструкторів. Ти, можливо, чуєш цей термін вперше, але насправді, напевно, користувався конструкторами, тільки сам не помічав цього :) Ми переконаємося в цьому пізніше.

Що таке конструктор Java і навіщо він потрібен?

Розглянемо два приклади.
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 407;

   }
}
Ми створабо наш автомобіль та встановабо для нього модель та максимальну швидкість. Однак у реальному проекті об'єкт Car явно матиме не 2 поля. А, наприклад, 16 полів!
public class Car {

   String model;//Модель
   int maxSpeed;//максимальна швидкість
   int wheels;//ширина дисків
   double engineVolume;//об'єм двигуна
   String color;//колір
   int yearOfIssue;//рік випуску
   String ownerFirstName;//ім'я власника
   String ownerLastName;//прізвище власника
   long price;//ціна
   boolean isNew;//нова чи ні
   int placesInTheSalon;//кількість місць у салоні
   String salonMaterial;//матеріал салону
   boolean insurance;// Чи застрахована
   String manufacturerCountry;//країна виробник
   int trunkVolume;//обсяг багажника
   int accelerationTo100km;//розгін до 100 км/год на секундах


   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";

   }

}
Ми створабо новий об'єкт Car . Одна проблема: полів у нас 16, а проініціалізували ми лише 12 ! Спробуй за кодом знайти ті, які ми забули! Не так просто, так? У такій ситуації програміст може легко помаботися та пропустити ініціалізацію якогось поля. У результаті поведінка програми стане помилковою:
public class Car {

   String model;//Модель
   int maxSpeed;//максимальна швидкість
   int wheels;//ширина дисків
   double engineVolume;//об'єм двигуна
   String color;//колір
   int yearOfIssue;//рік випуску
   String ownerFirstName;//ім'я власника
   String ownerLastName;//прізвище власника
   long price;//ціна
   boolean isNew;//нова чи ні
   int placesInTheSalon;//кількість місць у салоні
   String salonMaterial;//матеріал салону
   boolean insurance;// Чи застрахована
   String manufacturerCountry;//країна виробник
   int trunkVolume;//обсяг багажника
   int accelerationTo100km;//розгін до 100 км/год на секундах


   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("Модель Bugatti Veyron. Об'єм двигуна - " + bugatti.engineVolume + ", багажника - " + bugatti.trunkVolume + ", салон зроблений з " + bugatti.salonMaterial +
       ", ширина дисків -" + bugatti.wheels + ". Була придбана у 2018 році паном" + bugatti.ownerLastName);

   }

}
Виведення в консоль:
Модель Bugatti Veyron. Об'єм двигуна - 6.3, багажника - 0, салон зроблений з null, ширина дисків - 0. Була придбана в 2018 році паном null
Вашому покупцю, який віддав 2 мільйони доларів за машину, явно не сподобається, що його назвали "паном null"! А якщо серйозно, у результаті в нашій програмі виявився некоректно створений об'єкт — машина із шириною дисків 0 (тобто взагалі без дисків), відсутнім багажником, салоном, зробленим із невідомого матеріалу, та ще й комусь, що належить незрозуміло. Можна тільки уявити, як така помилка може "вистрілити" під час роботи програми! Нам треба якось уникнути таких ситуацій. Потрібно, щоб у нашій програмі було обмеження: при створенні нового об'єкта машини для нього завжди повинні бути вказані, наприклад, модель та максимальна швидкість. Інакше – не дозволяти створення об'єкта. З цим завданням легко справляються функції-конструктори. Вони одержали свою назву не просто так. Конструктор створює своєрідний "каркас" класу, якому кожен новий об'єкт класу має відповідати. Давай для зручності повернемося до простішого варіанту класуАвтомобіль з двома полями. З урахуванням наших вимог, конструктор для класу Car виглядатиме так:
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);
}
Зверни увагуяк створюється конструктор. Він схожий на звичайний метод, але у нього немає типу значення, що повертається. При цьому в конструкторі вказується назва класу, також з великої літери. У нашому випадку - Car . Крім того, в конструкторі використовується нове для тебе ключове слово this . "this" англійською - "цей, цього". Це слово свідчить про конкретний предмет. Код у конструкторі:
public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
можна перекласти майже дослівно: " model для цієї машини (яку ми зараз створюємо) = аргумент model , який вказаний в конструкторі. maxSpeed ​​для цієї машини (яку ми створюємо) = аргумент maxSpeed , який вказаний в конструкторі." Так і сталося:
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);
   }

}
Виведення в консоль:
Bugatti Veyron 407
Конструктор успішно надав потрібні значення. Ти, мабуть, помітив, що конструктор дуже схожий на звичайний метод! Так воно і є: конструктор - це метод, тільки трохи специфічний :) Так само як у метод, наш конструктор ми передали параметри. І так само, як виклик методу, виклик конструктора не спрацює, якщо їх не вказати:
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(); //помилка!
   }

}
Бачиш, конструктор зробив те, чого ми намагалися досягти. Тепер не можна створити машину без швидкості чи моделі! На цьому подібність конструкторів та методів не закінчується. Як і методи, конструктори можна перевантажувати. Уяви, що в тебе вдома живуть 2 коти. Одного з них ти взяв кошеням, а другого ти приніс додому з вулиці вже дорослим і не знаєш точно, скільки йому років. Отже, наша програма має вміти створювати котів двох видів — з ім'ям та віком для першого кота, і лише з ім'ям — для другого кота. Для цього ми перевантажимо конструктор:
public class Cat {

   String name;
   int age;

   //для першого кота
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   //для другого кота
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat barsik = new Cat("Barsik", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
До первісного конструктора з параметрами "ім'я" та "вік" ми додали ще один, тільки з ім'ям. Так само ми перевантажували методи в минулих уроках. Тепер ми успішно можемо створити обидва варіанти котів:) Навіщо потрібні конструктори?  - 2Пам'ятаєш, на початку лекції ми говорабо, що ти вже користувався конструкторами, тільки сам не помічав цього? Так і є. Справа в тому, що кожен клас Java має так званий конструктор за замовчуванням. У нього немає жодних аргументів, але він спрацьовує щоразу під час створення будь-якого об'єкта будь-якого класу.
public class Cat {

   public static void main(String[] args) {

       Cat barsik = new Cat(); //Ось тут спрацював конструктор за замовчуванням
   }
}
На перший погляд, це непомітно. Ну, створабо об'єкт і створабо, де тут робота конструктора? Щоб це побачити, давай прямо руками напишемо для класу Cat порожній конструктор, а всередині нього виведемо якусь фразу в консоль. Якщо вона виведеться, то конструктор відпрацював.
public class Cat {

   public Cat() {
       System.out.println("Створабо кота!");
   }

   public static void main(String[] args) {

       Cat barsik = new Cat(); //Ось тут спрацював конструктор за замовчуванням
   }
}
Виведення в консоль:
Створабо кота!
Ось і підтвердження! Конструктор за замовчуванням завжди незримо присутній у твоїх класах. Але тобі треба знати ще одну його особливість. Дефолтний конструктор зникає із класу, коли ти створюєш якийсь конструктор із аргументами. Доказ цього насправді ми вже бачабо вище. Ось у цьому коді:
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(); //помилка!
   }
}
Ми не змогли створити кота без імені та віку, тому що визначабо конструктор для Cat : рядок + число. Дефолтний конструктор одразу після цього зник із класу. Тому обов'язково запам'ятай: якщо тобі у твоєму класі потрібно кілька конструкторів, включаючи порожній, його потрібно створити окремо. Наприклад, ми створюємо програму для ветеринарної клініки. Наша клініка хоче робити добрі справи та допомагати бездомним котикам, про які ми не знаємо ні імені, ні віку. Тоді наш код має виглядати так:
public class Cat {

   String name;
   int age;

   //для домашніх котів
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   //для вуличних котів
   public Cat() {
   }

   public static void main(String[] args) {

       Cat barsik = new Cat("Barsik", 5);
       Cat streetCat = new Cat();
   }
}
Тепер, коли ми явно прописали конструктор за умовчанням, ми можемо створювати котів обох типів :) Для конструктора (як будь-якого методу) дуже важливий порядок дотримання аргументів. Поміняємо у нашому конструкторі аргументи імені та віку місцями.
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("Барсик", 10); //помилка!
   }
}
Помилка! Конструктор чітко описує: при створенні об'єкта Cat йому повинні бути передані число та рядок саме в такому порядку. Тож наш код не спрацьовує. Обов'язково запам'ятай це і зважай на створення своїх власних класів:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
Це два абсолютно різні конструктори! Якщо висловити в одному реченні відповідь на питання "Навіщо потрібен конструктор?", можна сказати: для того, щоб об'єкти завжди знаходабося в правильному стані. Коли ти використовуєш конструктори, всі твої змінні будуть коректно проініціалізовані, і в програмі не буде машин зі швидкістю 0 та інших неправильних об'єктів. Їхнє використання дуже вигідне насамперед для самого програміста. Якщо ти ініціалізуватимеш поля самостійно, великий ризик щось пропустити і помаботися. А з конструктором такого не буде: якщо ти передав у нього не всі потрібні аргументи або переплутав їх типи, компілятор одразу випустить помилку. Окремо варто сказати про те, що всередину конструктора не варто поміщати логіку програми. Для цього у твоєму розпорядженні є методи, в яких ти можеш описати весь потрібний тобі функціонал. Давай подивимося,
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("Наша автомобільна фабрика називається" + this.name);
   System.out.println("Вона була заснована" + this.age + " років назад" );
   System.out.println("За цей час на ній було зроблено" + this.carsCount +  " автомобілів");
   System.out.println("У середньому вона виробляє" + (this.carsCount/this.age) + " машин на рік");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
У нас є клас CarFactory , що описує фабрику з виробництва автомобілів. Усередині конструктора ми ініціалізуємо всі поля, і сюди поміщаємо логіку: виводимо в консоль деяку інформацію про фабрику. Здавалося б, нічого поганого в цьому немає, програма чудово відпрацювала. Виведення в консоль:
Наша автомобільна фабрика називається Ford Вона була заснована 115 років тому. За цей час на ній було вироблено 50000000 автомобілів. У середньому вона виробляє 434782 машин на рік
Але насправді ми заклали міну уповільненої дії. І такий код може дуже легко призвести до помилок. Уявімо, що тепер ми говоримо не про Ford, а про нову фабрику "Amigo Motors", яка існує менше року і виробила 1000 машин:
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("Наша автомобільна фабрика називається" + this.name);
   System.out.println("Вона була заснована" + this.age + " років назад" );
   System.out.println("За цей час на ній було зроблено" + this.carsCount +  " автомобілів");
   System.out.println("У середньому вона виробляє" + (this.carsCount/this.age) + " машин на рік");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
Виведення в консоль:
Наша автомобільна фабрика називається Amigo Motors Exception in thread "main" java.lang.ArithmeticException: / by zero Вона була заснована 0 років тому. CarFactory.main(CarFactory.java:23) Process finished with exit code 1</init>
Приїхали! Програма завершилася з якоюсь незрозумілою помилкою. Спробуєш здогадатися, у чому причина? Причина — у логіці, яку ми помістабо до конструктора. А саме — ось у цьому рядку:
System.out.println("У середньому вона виробляє" + (this.carsCount/this.age) + " машин на рік");
Тут ми виконуємо обчислення та ділимо кількість вироблених машин на вік фабрики. А оскільки наша фабрика нова (тобто їй 0 років) — у результаті виходить розподіл на 0, який у математиці заборонено. В результаті програма завершується помилково. Як же нам варто було вчинити? Винести всю логіку в окремий метод і назвати його, наприклад, printFactoryInfo() . Як параметр йому можна передати об'єкт CarFactory . Туди ж можна помістити всю логіку, і заразом — обробку можливих помилок, на зразок нашої з нулем років. Кожному своє. Конструктори необхідні коректного завдання стану об'єкта. Для бізнес-логіки ми маємо методи. Не варто змішувати одне з одним.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ