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Почему так важно хорошо разбираться в этом процессе? Представь, насколько неожиданными могут оказаться результаты создания обычного объекта, если не знать точно, что происходит «под капотом» :) Самое время вернуться к курсу и решить несколько задач! Удачи, и до новых встреч :)
Комментарии (171)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Дмитрий Уровень 20
5 июля 2024
Мне очень понравилось как в 15 лекции все разложили на два вида конструкторов. Статические конструкторы (которых как бы вроде как и нет в природе, но легче думать что есть). И они вызываются всегда первыми, когда класс загружается в память Нестатические конструкторы. И туда компилятором пихаются все поля класса и иницииализируются в порядке их описания в классе. И тогда логично, что сперва вызываются статические конструкторы всех классов в порядке наследования (то есть загружаются по сути в память), а дальше вызываются все нестатические конструкторы всех классов в порядке наследования. По моему так проще)
chess.rekrut Уровень 26
23 июня 2024
Спасибо! Теперь всё сразу стало ясно.
Light_Day :) Уровень 35
27 мая 2024
спасибо за статью!!
Saidash Уровень 27
6 мая 2024
Попытался наиболее подробно и понятно написать выжимку из множества статей. Может кому поможет, я буду рад. Загрузка класса (Единожды, при первичном использовании класса): Родитель 1) Определение статических полей родительского класса 2) Инициализация статических полей родительского класса и Выполнение статического блока родительского класса (!Равноправны, сверху вниз!) Наследник 3) Определение статических полей дочернего класса 4) Инициализация полей дочернего класса и Выполнение статического блока дочернего класса (!Равноправны, сверху вниз!) Создание объекта(Каждый раз при создании нового объекта): Родитель 1) Определение нестатических полей родительского класса 2) Инициализация нестатических полей родительского класса и Выполнение анонимного блока родительского класса(!Равноправны, сверху вниз!) 3) Выполнение кода конструктора родительского класса Наследник 4) Определение нестатических полей дочернего класса 5) Инициализация нестатических полей дочернего класса и Выполнение анонимного блока дочернего класса(!Равноправны, сверху вниз!) 6) Выполнение кода конструктора дочернего класса🤖
Private Joker Уровень 36
25 апреля 2024
Можно было лучше.
Filand Gor Уровень 34
6 марта 2024
А как же super() в конструктора потомка ???? С начала нужно вызвать вызвать конструктор потомка передать ему параметры/аргументы а потом и seper с этими параметрами вызвать ,а после продолжить выполнение конструктора потомка .
Zhandos Уровень 40
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)
Даниил Уровень 33
20 декабря 2023
мой тысячный лайк этой лекции, а лекция и в правду очень хорошая
Denis Gritsay Уровень 41
1 декабря 2023
1. Статические поля базового класса; 2. Статические поля дочернего класса; 3.Не статические поля базового класса; 4.Конструктор базового класса; 5.Не статические поля дочернего класса; 6.Конструктор дочернего класса; Логика - вначале по иерархии static поля, затем по иерархии классы, в которых в начале инициируются не статические поля а затем вызывается конструктор.