new — и все готово :) Здесь поговорим о том, что происходит внутри компьютера и Java-машины, когда мы пишем, например:
Cat cat = new Cat();
Мы ранее уже говорили об этом, но на всякий случай вспомним:
- Сначала для хранения объекта выделяется память.
- Далее Java-машина создает ссылку на этот объект (в нашем случае ссылка — это
Cat cat). - В завершение происходит инициализация переменных и вызов конструктора (этот процесс мы рассмотрим подробнее).
Первые два пункта особых вопросов вызывать не должны. Выделение памяти — процесс несложный, да и результат может быть только один из двух: либо память есть, либо ее нет :) Создание ссылки — тоже ничего необычного. А вот пункт номер три представляет из себя целый набор операций, идущих в строго определенном порядке.
Я не фанат зубрежки как средства что-то выучить, но вот в этом процессе тебе стоит хорошо разобраться, и знать этот порядок нужно наизусть.
Когда мы говорили о процессе создания объектов на прошлых лекциях, ты еще ничего толком не знал о наследовании, поэтому объяснить некоторые моменты было проблематично. Сейчас же объем твоих знаний достаточно велик, и мы, наконец, можем рассмотреть этот вопрос полноценно :)
Итак, третий пункт гласит что “в завершение происходит инициализация переменных и вызов конструктора.”
Но в каком порядке все это совершается?
Для лучшего понимания давай создадим два простейших класса — родителя и наследника:
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-машины:
Первое что произойдет — проинициализируются статические переменные класса
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После инициализации статических переменных класса-предка инициализируются статические переменные класса-потомка. То есть в нашем случае — поле
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начал свою работу.Время конструкторов все еще не пришло! Инициализация переменных продолжается. Третьими по счету будут инициализированы нестатические переменные класса-предка. Как видишь, наследование заметно усложняет процесс создания объекта, но тут уж ничего не поделаешь: некоторые вещи в программировании придется просто запомнить :)
Для эксперимента мы можем присвоить переменной
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уже было присвоенное значение.Наконец, дело дошло до конструкторов! Точнее, до конструктора базового класса. Начало его работы — четвертый пункт в процессе создания объекта.
Проверить это тоже достаточно легко. Попробуем вывести в консоль две строки: одну внутри конструктора базового
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!Отлично, значит мы не ошиблись :) Едем дальше.
Теперь пришла очередь инициализации нестатических полей класса-потомка, то есть нашего класса
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!Вызывается конструктор дочернего класса
Truck.И только сейчас, в последнюю очередь, будет вызван конструктор того класса, объект которого мы создаем!
Только на шестом этапе полям будут присвоены те значения, которые мы передадим в качестве параметров нашему грузовику.
Как видишь, «конструирование» грузовика, т.е. процесс создания объекта — штука непростая. Но мы, кажется, разобрали его до самых мелочей :)
Почему так важно хорошо разбираться в этом процессе?
Представь, насколько неожиданными могут оказаться результаты создания обычного объекта, если не знать точно, что происходит «под капотом» :)
Самое время вернуться к курсу и решить несколько задач!
Удачи, и до новых встреч :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ