JavaRush /Java блог /Random UA /Порядок дій під час створення об'єкта

Порядок дій під час створення об'єкта

Стаття з групи Random UA
Вітання! Сьогоднішня лекція буде досить...е-е-е...різносторонньою :) У тому сенсі, що ми розглянемо широке коло тем, але всі вони стосуватимуться процесу створення об'єкта . Ми розберемо його від початку до кінця: як викликаються конструктори, як і в якому порядку ініціалізуються поля (зокрема, статичні) тощо. Деякі моменти, що розглядаються у статті, ми торкалися раніше, тому можеш пробігти очима матеріал про конструктора базових класів . Спочатку давай згадаємо як відбувається створення об'єкта. Ну, як цей процес виглядає з погляду розробника, ти добре пам'ятаєш: створив клас, написав new— і все готове :) Тут поговоримо про те, що відбувається всередині комп'ютера та Java-машини, коли ми пишемо, наприклад:
Cat cat = new Cat();
Ми раніше вже говорабо про це, але про всяк випадок згадаємо:
  1. Спочатку для зберігання об'єкта виділяється пам'ять.
  2. Далі Java-машина створює посилання цей об'єкт (у разі посилання — це Cat cat).
  3. На завершення відбувається ініціалізація змінних та виклик конструктора (цей процес ми розглянемо докладніше).
Крім того, з лекції про життєвий цикл об'єкта ти напевно пам'ятаєш, що він триває доти, доки на нього існує хоч одне посилання. Якщо їх не залишилося, об'єкт стане здобиччю для збирача сміття. Порядок дій під час створення об'єкта - 2Перші два пункти особливих питань не повинні викликати. Виділення пам'яті - процес нескладний, та й результат може бути тільки один із двох: або пам'ять є, або її немає :) Створення посилання теж нічого незвичайного. А ось пункт номер три є цілим набором операцій, що йдуть у строго визначеному порядку. Я не фанат зубріння як засобу щось вивчити, але ось у цьому процесі тобі варто добре розібратися, і знати цей порядок треба напам'ять. Коли ми говорабо про процес створення об'єктів на минулих лекціях, ти ще нічого до пуття не знав про наслідування, тому пояснити деякі моменти було проблематично. Зараз же обсяг твоїх знань досить великий, і ми можемо розглянути це питання повноцінно :) Отже, третій пункт говорить що “ на завершення відбувається ініціалізація змінних та виклик конструктора. ” Але в якому порядку все це відбувається? Для кращого розуміння давай створимо два найпростіші класи — батька та спадкоємця:
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-машини:
  1. Перше, що станеться – проініціалізуються статичні змінні класу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
  2. Після ініціалізації статичних змінних класу-предка ініціалізуються статичні змінні класу-нащадка. Тобто в нашому випадку – поле 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розпочав свою роботу.

  3. Час конструкторів досі не настав! Ініціалізація змінних продовжується. Третіми за рахунком будуть ініціалізовані нестатичні змінні класу-предка. Як бачиш, успадкування помітно ускладнює процес створення об'єкта, але тут уже нічого не вдієш: деякі речі в програмуванні доведеться просто запам'ятати :)

    Для експерименту ми можемо привласнити змінної 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вже було надано значення.

  4. Зрештою, справа дійшла до конструкторів! Точніше, до архітектора базового класу. Початок роботи — четвертий пункт у процесі створення об'єкта.

    Перевірити це також досить легко. Спробуємо вивести в консоль два рядки: один усередині конструктора базового 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!

    Відмінно, значить, ми не помаболися :) Їдемо далі.

  5. Тепер настала черга ініціалізації нестатичних полів класу-нащадка , тобто нашого класу Truck. Поля класу, об'єкт якого ми створюємо, ініціалізуються лише на п'яту чергу! Дивно, але факт :) Знову ж таки, проведемо просту перевірку, таку ж, як і з батьківським класом: надамо змінній деяке початкове значення і перевіримо в конструкторі , що воно було присвоєно раніше, ніж конструктор почав роботу:maxSpeedTruck

    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!

  6. Викликається конструктор дочірнього класу Truck.

    І тільки зараз, в останню чергу, буде викликано конструктор того класу, який ми створюємо!

    Тільки на шостому етапі полям будуть присвоєні ті значення, які ми передамо як параметри нашій вантажівці.

    Як бачиш, «конструювання» вантажівки, тобто. процес створення об'єкта – штука непроста. Але ми, здається, розібрали його до дрібниць :)

Порядок дій під час створення об'єкта - 3Чому так важливо добре розумітися на цьому процесі? Уяви, наскільки несподіваними можуть стати результати створення звичайного об'єкта, якщо не знати точно, що відбувається «під капотом» :) Саме час повернутися до курсу і вирішити кілька завдань! Удачі, і до нових зустрічей :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ