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Почему так важно хорошо разбираться в этом процессе? Представь, насколько неожиданными могут оказаться результаты создания обычного объекта, если не знать точно, что происходит «под капотом» :) Самое время вернуться к курсу и решить несколько задач! Удачи, и до новых встреч :)
Комментарии (156)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Vitaly Demchenko Уровень 27
27 сентября 2023
Потрясающая лекция, Павел. Огромная благодарность. Видно, что подошел со всей душой.
Dmitry Vidonov Уровень 28 Expert
10 сентября 2023
Splendid work, Mr. Pavel!
GasTpoJlep #3197050 Уровень 26
3 сентября 2023
С кайфом написали
Mikhail Уровень 16
22 августа 2023
Супер статья Открыли глаза на процесс Спасибо вам Павел
Максим Уровень 15
6 июля 2023
Спасибо автору статьи за материал и разъяснение. У меня вопрос к JAVARASH: почему не позаботились о том, что бы рассказать зачем нужен статический блок инициализации и когда он инициализируется в предке и потомке? Вот отличный шортс на эту тему https://www.youtube.com/shorts/gBG8McovcDw
Ислам Уровень 32
20 июня 2023
Nice
Nikita Уровень 30
8 июня 2023
Напишу и сохраню, простите если в 100-ый раз тут пишут :) _____________________________________________________________________________________ 1. Статические переменные класса родителя (по умолчанию или с заданными значениями, если в коде выше статических блоков); 2. Статические блоки класса родителя; 3. Статические переменные класса (по умолчанию или с заданными значениями, если в коде выше статических блоков); 4. Статические блоки класса; 5. Не статические (у экземпляра) переменные класса родителя (по умолчанию или с заданными значениями, если в коде выше статических блоков); 6. Не статические (у экземпляра) блоки класса родителя; 7. Конструктор класса родителя; 8. Не статические (у экземпляра) переменные класса (по умолчанию или с заданными значениями, если в коде выше статических блоков); 9. Не статические (у экземпляра) блоки класса; 10. Конструктор класса; _____________________________________________________________________________________ Обращение только к статической переменной/функции (класс не был в памяти): а) Есть наследование: 1-4; б) Нет наследования: 3-4; Создание экзмепляра (класс не был в памяти): а) Есть наследование: 1-10; б) Нет наследования: 3,4,8,9,10; Создание экзмепляра (уже ранее был использован класс или ранее был создан аналогичный экзмепляр класса - класс был в памяти): а) Есть наследование: 5-10; б) Нет наследования: 8-10; P.S. чуть дальше будет отличная задача на эту тему: task1524
SplitCore Уровень 41
22 мая 2023
Статические переменные родителя-> статические переменные потомка-> нестатические переменные родителя-> конструктор родителя-> нестатические переменные потомка-> конструктор потомка
LeonidMessin22 Уровень 24
29 марта 2023
Еще можно поставить точку останова, запустить отладку и пройтись по шагам с помощью клавиши F7. Будет наглядно.
Murat Уровень 37
26 марта 2023
Прекрасная статья! Спасибо! Отлично закрепил данную тему) думаю теперь в голове надолго останется порядок: Статические поля базового класса -> Статические поля классов наследников -> Нестатические поля базового класса -> Конструктор базового класса -> Нестатические поля классов наследников -> Конструктор классов наследников