1. Ініціалізація змінних
Як вам уже відомо, ви можете у своєму класі оголосити кілька змінних класу, і не просто оголосити, а відразу ініціалізувати їх початковими значеннями.
Натомість ці самі змінні можна ініціалізувати і в конструкторі. Тому теоретично можлива ситуація, коли одним і тим самим змінним класу присвоюються значення двічі. Приклад
Код | Примітка |
---|---|
|
Змінній age присвоюється початкове значення Початкове значення затирається Для age використовується початкове значення |
|
Так можна: буде викликано перший конструктор |
|
Так можна: буде викликано другий конструктор |
От що відбуватиметься під час виконання коду Cat cat = new Cat("Мурчик", 2);
:
- Створюється об'єкт типу
Cat
. - Ініціалізуються всі змінні класу своїми початковими значеннями.
- Викликається конструктор і виконується його код.
Тобто змінні класу спочатку ініціалізуються своїми значеннями, а вже потім виконується код конструкторів.
2. Порядок ініціалізації змінних класу
Змінні не просто ініціалізуються до початку роботи конструктора: вони ще й ініціалізуються в чітко визначеному порядку — порядку їх оголошення в класі.
Розгляньмо отакий цікавий код:
Код | Примітка |
---|---|
|
Такий код не скомпілюється, оскільки на момент створення змінної а
змінних b
і c
ще не існує. А отак записати можна, і цей код чудово скомпілюється і працюватиме.
Код | Примітка |
---|---|
|
0 0+2 0+2+3 |
Примітка. Сподіваюсь, ви пам'ятаєте, що ваш код має бути прозорим для інших розробників, отож такі прийоми краще не використовувати — це погіршує читомість коду.
Не забувайте, що всі змінні класу, перш ніж їм присвоять певні значення, мають значення за замовчуванням. Для типу int
це нуль.
Коли віртуальна машина Java ініціалізуватиме змінну а
, вона просто присвоїть їй значення за замовчуванням для типу int — 0.
Коли черга дійде до b
, змінна a
вже буде відома й міститиме значення, тому JVM присвоїть змінній b
значення 2.
Ну а коли дійдеться до змінної c
, змінні а
і b
вже будуть проініціалізовані, і JVM легко обчислить початкове значення для с
: 0+2+3.
Якщо ви створили змінну всередині методу, ви не можете її використовувати, не присвоївши їй перед цим певне значення. А зі змінними класу це не так. Якщо змінній класу не присвоєно початкове значення, значить, їй присвоюється значення за замовчуванням.
3. Константи
Коли ми вже продовжуємо розбиратися в процесі створення об'єкта, варто торкнутися питання ініціалізації констант — змінних класу, які мають модифікатор final
.
Якщо змінна класу має модифікатор final
, їй має бути присвоєно початкове значення. Це ви вже знаєте, і тут немає нічого дивного.
А от чого ви не знаєте: початкове значення можна не присвоювати відразу, якщо присвоїти його в конструкторі. І це чудово працюватиме для final-змінної. Єдина вимога — якщо конструкторів декілька, то final-змінній має бути присвоєно значення у всіх конструкторах.
Приклад:
public class Cat
{
public final int maxAge = 25;
public final int maxWeight;
public Cat (int weight)
{
this.maxWeight = weight; // внесення початкового значення в константу
}
}
4. Код у конструкторі
І ще кілька важливих зауважень щодо конструкторів. Згодом у процесі вивчення Java ви зіткнетеся з такими речами як успадкування, серіалізація, винятки тощо. Усі вони по-різному впливають на роботу конструкторів. Наразі немає сенсу дуже заглиблюватися в ці теми, але принаймні зачепити їх ми мусимо.
От, наприклад, одне з вагомих зауважень. Теоретично в конструкторі можна писати код будь-якої складності. Однак не треба цього робити. Приклад:
|
Відкриваємо потік читання файлу Зчитуємо файл у масив байтів Зберігаємо масив байтів у вигляді рядка Виводимо вміст файлу на екран |
У конструкторі класу FilePrinter ми одразу відкрили байтовий потік до файлу і прочитали його вміст. Це досить складна поведінка, яка потенційно може спричинити помилки.
А якби такого файлу не було? А якби були проблеми з його читанням? А якби він був занадто великим?
Складна логіка передбачає велику ймовірність помилок і код, який має правильно обробляти винятки.
Приклад 1 — Серіалізація
У стандартній Java-програмі є багато ситуацій, коли об'єкти вашого класу створюєте не ви. Наприклад, ви хочете передати об'єкт мережею: у такому разі Java-машина сама перетворить ваш об'єкт на набір байтів, передасть його та знову за набором байтів створить об'єкт.
І отут виявиться, що на іншому комп'ютері немає вашого файлу, в конструкторі виникне помилка, і ніхто її не обробить — усе це разом може призвести до закриття програми.
Приклад 2 — Ініціалізація полів класу
Якщо конструктор вашого класу може кинути checked-виняток (містить ключове слово throws), ви повинні перехопити цей виняток у методі, який створює ваш об'єкт.
А якщо такого методу немає? Приклад:
Код | Примітка |
---|---|
|
Такий код не скомпілюється. |
Конструктор класу FilePrinter
містить checked-винятки: ви не можете створити об'єкт FilePrinter
, не обгорнувши його конструкцією try-catch. А try-catch можна писати тільки в методі.
5. Конструктор базового класу
У попередніх лекціях ми трохи обговорювали успадкування. На жаль, докладно обговорювати успадкування та ООП ми будемо на рівні, присвяченому ООП, а конструкторів це стосується вже зараз.
Якщо ви успадкуєте свій клас від іншого класу, фактично в об'єкт вашого класу буде вбудовано об'єкт батьківського класу, причому цей батьківський клас має свої змінні класу та свої конструктори.
Тому вам дуже важливо знати й розуміти, як відбуваються ініціалізація параметрів і виклик конструкторів, коли ваш клас має батьківський клас, чиї змінні та методи ви успадковуєте.
Класи
Як же нам дізнатися, в якому порядку ініціалізуються змінні й викликаються конструктори? Для початку напишімо код двох класів, один з яких успадковується від іншого:
Код | Примітка |
---|---|
|
Клас ChildClass успадковується від класу ParentClass . |
Нам треба визначити, в якому порядку ініціалізуються змінні й викликаються конструктори. Зробити це допоможе логування.
Логування
Логуванням називається процес запису в консоль або у файл тих дій, які відбуваються під час роботи програми.
Визначити, що відбувся виклик конструктора, досить просто: потрібно в тілі конструктора вивести в консоль повідомлення про це. А от як визначити, що змінну ініціалізовано?
Насправді це теж не дуже складно: потрібно написати спеціальний метод, котрий повертатиме значення, яким ініціалізується змінна класу, і залогувати цей факт. Цей код може мати, приміром, такий вигляд:
Остаточний код
|
Створюємо об'єкт типу ChildClass Цей метод записує в консоль переданий текст і повертає його Оголошуємо ParentClass Пишемо текст і ним же ініціалізуємо змінні Записуємо в консоль повідомлення про виклик конструктора. Значення, що повертається, ігноруємо. Оголошуємо ChildClass Пишемо текст і ним же ініціалізуємо змінні Записуємо в консоль повідомлення про виклик конструктора. Значення, що повертається, ігноруємо. |
Якщо виконати цей код, на екран буде виведено текст:
Виведення на екран методу Main.print() |
---|
|
Отже, ви завжди можете особисто переконатися, що змінні класу ініціалізуються до виклику його конструктора. Уся ініціалізація членів базового класу відбувається до ініціалізації членів класу-спадкоємця.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ