Создание объектов: детальное изучение

🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!

Всем привет! Сегодня мы затронем широкий круг тем, связанных с процессом создания объектов.

Мы проанализируем этот процесс от начала до конца, изучим, как вызываются конструкторы, как и в каком порядке инициализируются поля (в том числе статические) и т. д.

Небольшое введение

Для начала вспомним, как создается объект.

С точки зрения разработчика этот процесс довольно простой: создаём класс, пишем new, и все готово 🙂 А что происходит внутри компьютера и Java-машины (JVM)? Например, мы пишем:

Cat cat = new Cat();
  • Сначала выделяется память для хранения объекта.
  • Затем Java-машина создает ссылку на объект (в нашем случае это кошка).
  • Наконец, переменные инициализируются и вызывается конструктор (мы рассмотрим этот процесс более подробно).

Важно: объект существует до тех пор, пока есть хотя бы одна ссылка на него. Если их не остается, то объект становится добычей сборщика мусора.

Особого внимания заслуживает третий пункт: он представляет собой целый набор операций, выполняемых в строгом порядке.

Чтобы понять, в каком порядке что происходит, создадим два простых класса – родительский и дочерний:

public class Vehicle {

   public static int vehicleCounter = 0;

   private String description = "Vehicle";
   public Vehicle() {
   }

   public String getDescription() {
       return description;
   }
}

public class Truck extends Vehicle {

   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;

       Vehicle.vehicleCounter++;
       truckCounter++;
   }
}
Run Code

Класс Truck позволяет создать совокупность характеристик грузового автомобиля; поля класса содержат год выпуска, модель транспорта и максимальную скорость (см. строки 18-20).

Допустим, мы хотим создать такой объект:

public class Main {

   public static void main(String[] args) throws IOException {

       Truck truck = new Truck(2017, "Scania S 500 4x2", 220);
   }
}
Run Code

Для Java-машины этот процесс будет выглядеть совершенно другим образом.

Шаг 1

Сначала, до вызова конструкторов, инициализируются статические переменные класса Vehicle. Обратите внимание, что процесс начинается не с дочернего, а с родительского класса. Чтобы подтвердить это, установим поле vehicleCounter в классе Vehicle (строка 3) и попробуем вывести его значение как в конструкторах Vehicle, так и в конструкторах Truck.

public class Vehicle {

   public static int vehicleCounter = 10;
   private String description = "Vehicle";

   public Vehicle() {
       System.out.println(vehicleCounter);
   }

   public String getDescription() {
       return description;
   }
}

public class Truck extends Vehicle {

   private static int truckCounter = 0;

   private int yearOfManufacture;
   private String model;
   private int maxSpeed;

   public Truck(int yearOfManufacture, String model, int maxSpeed) {
       System.out.println(vehicleCounter);
       this.yearOfManufacture = yearOfManufacture;
       this.model = model;
       this.maxSpeed = maxSpeed;

       Vehicle.vehicleCounter++;
       truckCount++;
   }
}
Run Code

Мы специально поместили оператор println в самое начало конструктора Truck, чтобы убедиться, что на момент вывода vehicleCounter поля еще не были инициализированы.

Вот результат:

10
10

Шаг 2

После инициализации статических переменных родительского класса инициализируются статические переменные дочернего класса. В нашем случае это поле truckCounter класса Truck (строка 17).

Проведем еще один эксперимент: попробуем вывести значение truckCounter внутри конструктора Truck до инициализации других полей.

public class Truck extends Vehicle {

   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;

       Vehicle.vehicleCounter++;
       truckCounter++;
   }
}
Run Code

Итак, значение 10 уже присвоено нашей статической переменной в начале работы конструктора Truck.

Шаг 3

Далее инициализируются нестатические переменные родительского класса.

Эксперимента ради присвоим некоторое начальное значение переменной description в классе Vehicle, а затем изменим его в конструкторе:

public class Vehicle {

   public static int vehicleCounter = 10;

   private String description = "Initial value of the description field";

   public Vehicle() {
       System.out.println(description);
       description = "Vehicle";
       System.out.println(description);
   }

   public String getDescription() {
       return description;
   }
}
Run Code

Запустив метод main(), создадим объект класса Truck:

public class Main {

   public static void main(String[] args) throws IOException {

       Truck truck = new Truck(2017, "Scania S 500 4x2", 220);
   }
}
Run Code

В качестве результата получим:

Initial value of the description field
Vehicle

Это доказывает, что в момент начала работы конструктора Vehicle полю description уже присвоено значение.

Шаг 4

Итак, пришло время конструкторов! Сначала вызывается конструктор родительского класса, что тоже можно легко проверить.

Попробуем вывести в консоль две строки: одну зададим внутри конструктора родительского класса Vehicle, вторую – внутри конструктора Truck. Предполагается, что строка, заданная внутри Vehicle, будет выведена первой:

public Vehicle() {

   System.out.println("Hello from the Vehicle constructor!");
}

public Truck(int yearOfManufacture, String model, int maxSpeed) {

   System.out.println("Hello from the Truck constructor!");
   this.yearOfManufacture = yearOfManufacture;
   this.model = model;
   this.maxSpeed = maxSpeed;

   Vehicle.vehicleCounter++;
   truckCounter++;
}
Run Code

Запустим метод main() и посмотрим на результат:

Hello from the Vehicle constructor!
Hello from the Truck constructor!

Наша гипотеза оказалась верной 🙂

Шаг 5

Настало время для инициализации нестатических полей дочернего класса (в нашем случае – класса Truck).

Присвоим переменной maxSpeed некоторое начальное значение и в конструкторе Truck проверим, что значение было присвоено еще до запуска конструктора:

public class Truck extends Vehicle {

   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("Initial value of maxSpeed = " + this.maxSpeed);
       this.yearOfManufacture = yearOfManufacture;
       this.model = model;
       this.maxSpeed = maxSpeed;

       Vehicle.vehicleCounter++;
       truckCounter++;
   }
}
Run Code

Консольный вывод:

Initial value of maxSpeed = 150

Действительно, когда запускается конструктор Truck, значение maxSpeed уже равно 150!

Шаг 6

Наконец, только на последнем шаге вызывается конструктор дочернего класса Truck. Полям объекта присваиваются значения, которые мы передадим в конструктор.

Процесс создания объекта – дело нелегкое, но мы довольно детально изучили, что происходит “под капотом” 🙂

Для вас также быть полезной статья «Классы и объекты в Java»

Перевод статьи «Sequence of actions during object creation».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх