new — і все готово :) Тут поговоримо про те, що відбувається всередині комп'ютера та Java-машини, коли ми пишемо, наприклад:
Cat cat = new Cat();
Ми раніше вже говорили про це, але на всяк випадок згадаємо:
- Спочатку для зберігання об'єкта виділяється пам'ять.
- Далі Java-машина створює посилання на цей об'єкт (у нашому випадку посилання — це
Cat cat). - На завершення відбувається ініціалізація змінних та виклик конструктора (цей процес ми розглянемо детальніше).
Перші два пункти особливих запитань викликати не повинні. Виділення пам'яті — процес нескладний, і результат може бути тільки один із двох: або пам'ять є, або її немає :) Створення посилання — теж нічого незвичайного. А ось пункт номер три являє собою цілий набір операцій, які виконуються у строго визначеному порядку.
Я не фанат зубріння як способу щось вивчити, але от у цьому процесі тобі варто добре розібратися, і знати цей порядок потрібно напам'ять.
Коли ми говорили про процес створення об'єктів на попередніх лекціях, ти ще нічого толком не знав про спадкування, тому пояснити деякі моменти було проблематично. Зараз же обсяг твоїх знань достатньо великий, і ми, нарешті, можемо розглянути це питання повноцінно :)
Отже, третій пункт каже що “на завершення відбувається ініціалізація змінних та виклик конструктора.”
Але в якому порядку все це відбувається?
Для кращого розуміння давай створимо два найпростіших класи — батьківський і клас-нащадок:
public class Car {
public static int carCounter = 0;
private String description = "Абстрактна машина";
public Car() {
}
public String getDescription() {
return description;
}
}
public class Truck extends Car {
private static int truckCounter = 0;
private int yearOfManufacture;
private String model;
private int maxSpeed;
public Truck(int yearOfManufacture, String model, int maxSpeed) {
this.yearOfManufacture = yearOfManufacture;
this.model = model;
this.maxSpeed = maxSpeed;
Car.carCounter++;
truckCounter++;
}
}
Клас Truck представляє собою реалізацію вантажівки: з полями, які відображають її рік випуску, модель і максимальну швидкість.
Отже, ми хочемо створити один такий об'єкт:
public class Main {
public static void main(String[] args) throws IOException {
Truck truck = new Truck(2017, "Scania S 500 4x2", 220);
}
}
Ось як буде виглядати цей процес з точки зору Java-машини:
Перше, що відбудеться — проініціалізуються статичні змінні класу
Car. Так-так, саме класуCar, а не Truck. Статичні змінні ініціалізуються ще до виклику конструкторів, і починається це у класі-батьку. Давай спробуємо перевірити. Виставимо лічильникcarCounterу класіCarна 10 і спробуємо вивести його у консоль в обох конструкторах —CarіTruck.public class Car { public static int carCounter = 10; private String description = "Абстрактна машина"; public Car() { System.out.println(carCounter); } public String getDescription() { return description; } } public class Truck extends Car { private static int truckCount = 0; private int yearOfManufacture; private String model; private int maxSpeed; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println(carCounter); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Car.carCounter++; truckCount++; } }Ми спеціально поставили вивід у консоль на самому початку конструктора
Truck, щоб точно знати: поля вантажівки на момент виводуcarCounterу консоль ще не були ініціалізовані.А ось і результат:
10 10Після ініціалізації статичних змінних класу-батька ініціалізуються статичні змінні класу-нащадка. Тобто у нашому випадку — поле
truckCounterкласуTruck.Знову ж таки, проведемо експеримент і спробуємо вивести значення
truckCounterвсередині конструктораTruckдо ініціалізації інших полів:public class Truck extends Car { private static int truckCounter = 10; private int yearOfManufacture; private String model; private int maxSpeed; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println(truckCounter); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Car.carCounter++; truckCounter++; } }Як бачиш, значення 10 вже було присвоєне нашій статичній змінній у момент, коли конструктор
Truckрозпочав свою роботу.Час конструкторів ще не настав! Ініціалізація змінних триває. Третіми за чергою будуть ініціалізовані нестатичні змінні класу-батька. Як бачиш, наслідування суттєво ускладнює процес створення об'єкта, але тут нічого не вдієш: деякі речі в програмуванні доведеться просто запам'ятати :)
Для експерименту ми можемо присвоїти змінній
descriptionкласуCarякесь початкове значення, а потім змінити його у конструкторі.public class Car { public static int carCounter = 10; private String description = "Початкове значення поля description"; public Car() { System.out.println(description); description = "Абстрактна машина"; System.out.println(description); } public String getDescription() { return description; } }Запустимо наш метод
main()зі створенням вантажівки:public class Main { public static void main(String[] args) throws IOException { Truck truck = new Truck(2017, "Scania S 500 4x2", 220); } }І отримаємо результат:
Початкове значення поля description Абстрактна машинаЦе доводить, що на момент початку роботи конструктора
Carу поляdescriptionвже було присвоєне значення.Нарешті, справа дійшла до конструкторів! Точніше, до конструктора базового класу. Початок його роботи — четвертий пункт у процесі створення об'єкта.
Перевірити це також достатньо просто. Спробуємо вивести у консоль два рядки: один всередині конструктора базового
Car, другий — всередині конструктораTruck. Нам потрібно переконатися, що рядок всерединіCarвиведеться першим:public Car() { System.out.println("Привіт з конструктора Car!"); } public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println("Привіт з конструктора Truck!"); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Car.carCounter++; truckCounter++; }Запускаємо наш метод
main()і дивимося на результат:Привіт з конструктора Car! Привіт з конструктора Truck!Чудово, значить ми не помилилися :) Їдемо далі.
Тепер черга ініціалізації нестатичних полів класу-нащадка, тобто нашого класу
Truck. Поля класу, об'єкт якого ми створюємо, ініціалізуються тільки у п'яту чергу! Дивовижно, але факт :) Знову ж таки, проведемо просту перевірку, таку ж, як і з батьківським класом: присвоїмо зміннійmaxSpeedякесь початкове значення і перевіримо у конструкторіTruck, що воно було присвоєне раніше, ніж конструктор почав роботу:public class Truck extends Car { private static int truckCounter = 10; private int yearOfManufacture; private String model; private int maxSpeed = 150; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println("Початкове значення maxSpeed = " + this.maxSpeed); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Car.carCounter++; truckCounter++; } }Вивід у консоль:
Початкове значення maxSpeed = 150Як бачиш, на момент старту конструктора
TruckзначенняmaxSpeedвже було рівне 150!Викликається конструктор дочірнього класу
Truck.І тільки зараз, в останню чергу, буде викликаний конструктор того класу, об'єкт якого ми створюємо!
Тільки на шостому етапі полям будуть присвоєні ті значення, які ми передамо як параметри нашій вантажівці.
Як бачиш, «конструювання» вантажівки, тобто процес створення об'єкта — штука непроста. Але ми, здається, розібрали його до найменших деталей :)
Чому так важливо добре розуміти цей процес?
Уяви, наскільки несподіваними можуть виявитися результати створення звичайного об'єкта, якщо не знати точно, що відбувається «під капотом» :)
Саме час повернутися до курсу і розв'язати кілька задач!
Удачі, і до нових зустрічей :)
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ