JavaRush /Java блог /Java Developer /Порядок действий при создании объекта
Автор
Pavlo Plynko
Java-разработчик в CodeGym

Порядок действий при создании объекта

Статья из группы Java Developer
Привет! Сегодняшняя лекция будет довольно...э-э-э...разносторонней :) В том смысле, что мы рассмотрим широкий круг тем, но все они будут касаться процесса создания объекта. Мы разберем его от начала до конца: как вызываются конструкторы, как и в каком порядке инициализируются поля (в том числе статические) и т.д. Некоторые моменты, рассматриваемые в статье, мы затрагивали ранее, поэтому можешь пробежать глазами материал о конструкторе базовых классов. Для начала давай вспомним как происходит создание объекта. Ну, как этот процесс выглядит с точки зрения разработчика, ты хорошо помнишь: создал класс, написал 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’a в консоль еще не были инициализированы.

    А вот и результат:

    
    10
    10
    
  2. После инициализации статических переменных класса-предка инициализируются статические переменные класса-потомка. То есть в нашем случае — поле truckCounter класса Truck.

    Опять же, проведем эксперимент и попробуем вывести значение truckCounter’a внутри конструктора 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. Поля класса, объект которого мы создаем, инициализируются только в пятую очередь! Удивительно, но факт :) Опять же, проведем простую проверку, такую же, как и с родительским классом: присвоим переменной 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!

  6. Вызывается конструктор дочернего класса Truck.

    И только сейчас, в последнюю очередь, будет вызван конструктор того класса, объект которого мы создаем!

    Только на шестом этапе полям будут присвоены те значения, которые мы передадим в качестве параметров нашему грузовику.

    Как видишь, «конструирование» грузовика, т.е. процесс создания объекта — штука непростая. Но мы, кажется, разобрали его до самых мелочей :)

Порядок действий при создании объекта - 3Почему так важно хорошо разбираться в этом процессе? Представь, насколько неожиданными могут оказаться результаты создания обычного объекта, если не знать точно, что происходит «под капотом» :) Самое время вернуться к курсу и решить несколько задач! Удачи, и до новых встреч :)
Комментарии (165)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Filand Gor Уровень 29
6 марта 2024
А как же super() в конструктора потомка ???? С начала нужно вызвать вызвать конструктор потомка передать ему параметры/аргументы а потом и seper с этими параметрами вызвать ,а после продолжить выполнение конструктора потомка .
Zhandos Уровень 29
17 февраля 2024
1 инициализация статических переменных базового класса 2 инициализация статических переменных дочернего класс 3 инициализация нестатических переменных базового класса 4 вызов конструктора базового класса 5 инициализация нестатических переменных дочернего класса 6 вызов конструктора дочернего класса
Alex Уровень 28
3 января 2024
Object -> Object -> Object -> ... -> Object 1. Static -> Static -> Static -> ... Static 2. (Non Static, Constructor) -> (Non Static, Constructor) -> (Non Static, Constructor) -> ... -> (Non Static, Constructor)
Даниил Уровень 29
20 декабря 2023
мой тысячный лайк этой лекции, а лекция и в правду очень хорошая
Denis Gritsay Уровень 35
1 декабря 2023
1. Статические поля базового класса; 2. Статические поля дочернего класса; 3.Не статические поля базового класса; 4.Конструктор базового класса; 5.Не статические поля дочернего класса; 6.Конструктор дочернего класса; Логика - вначале по иерархии static поля, затем по иерархии классы, в которых в начале инициируются не статические поля а затем вызывается конструктор.
Вячеслав Уровень 28
11 ноября 2023
Запоминалка. Порядок действий при создании объекта
hidden #3204938 Уровень 28
26 октября 2023
Как к себе домой залетела лекция. Вот таким бы ребятам условия задач писать.
Vitaly Demchenko Уровень 44
27 сентября 2023
Потрясающая лекция, Павел. Огромная благодарность. Видно, что подошел со всей душой.
Dmitry Vidonov Уровень 29 Expert
10 сентября 2023
Splendid work, Mr. Pavel!
GasTpoJlep #3197050 Уровень 28
3 сентября 2023
С кайфом написали