Порядок загрузки классов, статические данные…
— Привет, Амиго! Слышала, Риша рассказал тебе новую и очень интересную тему?!
— Моя тема будет не менее интересной. Я хочу рассказать тебе о загрузке в память классов.
Классы в Java – это файлы на диске, содержащие байт-код – скомпилированный Java-код.
— Java-машина не загружает их без необходимости. Как только где-то в коде происходит обращение к классу, Java-машина проверяет – загружен ли он. И если нет, то загружает и инициализирует его.
Инициализация класса – это присваивание значений всех его статических переменных и вызов всех статических блоков.
— Похоже на вызов конструктора у объекта. А что такое статический блок?
— Если для инициализации переменных объекта нужно выполнить сложный код (например, что-то загрузить из файла), мы можем сделать это в конструкторе. Статические переменные такой возможности лишены. Но т.к. потребность в этом осталась, в классы можно добавить статический блок или блоки, которые по сути своей эквивалентны вызову статических конструкторов.
class Cat < public static int catCount = 0 ; public static String namePrefix; static < Properties p = new Properties(); p.loadFromFile("cat.properties"); namePrefix = p.get("name-prefix"); >public static int maxCatCount = 50; static < Properties p = new Properties(); p.loadFromFile("max.properties"); if (p.get("cat-max") != null) maxCatCount = p.getInt("cat-max"); >>
Внимание! Объявляется набор кода на JavaRush. Включите режим повышенной внимательности, расслабьте пальцы, читайте код и… набирайте его в соответствующем окошке. Набор кода — не бесполезное занятие, как может показаться на первый взгляд: вы привыкаете к синтаксису и запоминаете его (современные IDE редко дают вам это сделать).
Это очень похоже на то, что происходит при вызове конструктора. Я даже записала это в виде несуществующего статического конструктора.
Самый популярный комментарий рекомендует эту ссылку http://vscode.ru/prog-lessons/staticheskiy-blok-v-java.html НО тем кто не понял советую блок статик из примера в статье поставить в конец и вы увидите что sout вывел его раньше именно за счет того что кусок кода в блоке статик JVM прочитает раньше остального класса.
Несколько слов о порядке инициалиазации. Может быть полезным для тех, кто пытается собрать всю информацию воедино. При первом обращении к классу выполняются все статические инициализаторы и блоки инициализации в порядке их появления в коде. Причём изначально выполняются статические блоки инициализации и инициализаторы всех предков, начиная с базового класса, если целевой класс принимает участи в иерарахии наследования. P.S. Под первым обращением к классу подразумевается любое взаимодействие с классом. Таковым может быть прямое обращение к полям или методам класса, а может быть и использование ссылочной переменной соответствующего типа (в нашем случае идёт речь о переменной рассматриваемого с контексте инициализации типа). Как уже написал, порядок имеет имеет значение. Если в коде идёт поле, за которым следует блок, а потом снова поле, то инициализация будет выполняться именно в таком порядке. Полям в любом случае изначально присваиваются значения по умолчанию, а потом уже те значения, которые были указаны непосредственно в коде. Когда мы вызываем конструктор класса (при создании нового экземпляра класса), то изначально выделяется память под новый объект. Это означает, что мы выделяем место под хранение всех полей объекта, которым на начальном этапе будут присвоены значения по умолчанию. После этого срабатывают инициализаторы полей и блоки иницилазиации экземпляра (это такие же блоки, только без ключевого слова static), после чего отрабатывает код из конструктора. Двигаемся мы как бы слоями, начиная с самого базового класса и спускаясь вниз к целевому классу.
Порядок действий при создании объекта
Привет! Сегодняшняя лекция будет довольно. э-э-э. разносторонней 🙂 В том смысле, что мы рассмотрим широкий круг тем, но все они будут касаться процесса создания объекта . Мы разберем его от начала до конца: как вызываются конструкторы, как и в каком порядке инициализируются поля (в том числе статические) и т.д. Некоторые моменты, рассматриваемые в статье, мы затрагивали ранее, поэтому можешь пробежать глазами материал о конструкторе базовых классов. Для начала давай вспомним как происходит создание объекта. Ну, как этот процесс выглядит с точки зрения разработчика, ты хорошо помнишь: создал класс, написал new — и все готово 🙂 Здесь поговорим о том, что происходит внутри компьютера и Java-машины, когда мы пишем, например:
Мы ранее уже говорили об этом, но на всякий случай вспомним:
- Сначала для хранения объекта выделяется память.
- Далее 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 представляет собой реализацию грузовика: с полями, отражающими его год выпуска, модель и максимальную скорость. Итак, мы хотим создать один такой объект:
- Первое что произойдет — проинициализируются статические переменные класса 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 в консоль еще не были инициализированы. А вот и результат:
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++; >>
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; >>
Начальное значение поля description Абстрактная машина
public Car() < System.out.println("Привет из конструктора Car!"); >public Truck(int yearOfManufacture, String model, int maxSpeed)
Привет из конструктора Car! Привет из конструктора 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 text-bold">на момент старта конструктора Truck
значение maxSpeed
уже было равно 150!
Вызывается конструктор дочернего класса Truck
.
И только сейчас, в последнюю очередь, будет вызван конструктор того класса, объект которого мы создаем!
Только на шестом этапе полям будут присвоены те значения, которые мы передадим в качестве параметров нашему грузовику.
Как видишь, «конструирование» грузовика, т.е. процесс создания объекта — штука непростая. Но мы, кажется, разобрали его до самых мелочей :)
Почему так важно хорошо разбираться в этом процессе? Представь, насколько неожиданными могут оказаться результаты создания обычного объекта, если не знать точно, что происходит «под капотом» :) Самое время вернуться к курсу и решить несколько задач! Удачи, и до новых встреч :)