«1. Обзор
В этом кратком руководстве мы сосредоточимся на типе возвращаемого значения для конструктора в Java.
Сначала мы познакомимся с тем, как работает инициализация объектов в Java и JVM. Затем мы копнем глубже, чтобы увидеть, как инициализация и присваивание объектов работают «под капотом».
2. Инициализация экземпляра
Начнем с пустого класса:
public class Color {}
Здесь мы собираемся создать экземпляр из этого класса и присвоить его некоторой переменной:
Color color = new Color();
After скомпилировав этот простой фрагмент Java, давайте взглянем на его байт-код с помощью команды javap -c:
0: new #7 // class Color
3: dup
4: invokespecial #9 // Method Color."<init>":()V
7: astore_1
Когда мы создаем экземпляр объекта в Java, JVM выполняет следующие операции:
- First, it finds a place in its process space for the new object.
- Then, the JVM performs the system initialization process. In this step, it creates the object in its default state. The new opcode in the bytecode is actually responsible for this step.
- Finally, it initializes the object with the constructor and other initializer blocks. In this case, the invokespecial opcode calls the constructor.
Как показано выше, метод подпись для конструктора по умолчанию:
Method Color."<init>":()V
\u003cinit\u003e — это имя методов инициализации экземпляра в JVM. В данном случае \u003cinit\u003e — это функция, которая:
-
ничего не принимает на вход (пустые круглые скобки после имени метода) ничего не возвращает (V означает пустоту)
Таким образом, возвращаемый тип конструктора в Java и JVM недействительна.
Еще раз взглянем на наше простое присваивание:
Color color = new Color();
Теперь, когда мы знаем, что конструктор возвращает void, давайте посмотрим, как работает присваивание.
3. Как работает назначение
JVM — это виртуальная машина на основе стека. Каждый стек состоит из кадров стека. Проще говоря, каждый кадр стека соответствует вызову метода. На самом деле, JVM создает фреймы с помощью вызова нового метода и уничтожает их, когда они завершают свою работу:
Каждый стековый фрейм использует массив для хранения локальных переменных и стек операндов для хранения частичных результатов. Учитывая это, давайте еще раз взглянем на байт-код:
0: new #7 // class Color
3: dup
4: invokespecial #9 // Method Color."<init>":()V
7: astore_1
Вот как работает присваивание:
-
Новая инструкция создает экземпляр Color и помещает его ссылку в стек операндов. Код операции dup дублирует последний элемент в стек операндов. invokespecial берет дублированную ссылку и использует ее для инициализации. После этого в стеке операндов остается только исходная ссылка. astore_1 хранит исходную ссылку на индекс 1 массива локальных переменных. Префикс «a» означает, что сохраняемый элемент является ссылкой на объект, а «1» — это индекс массива
С этого момента второй элемент (индекс 1) в массиве локальных переменных является ссылкой к только что созданному объекту. Поэтому мы не теряем ссылку, и присваивание действительно работает — даже когда конструктор ничего не возвращает!
4. Заключение
В этом кратком руководстве мы узнали, как JVM создает и инициализирует экземпляры нашего класса. Более того, мы увидели, как инициализация экземпляра работает «под капотом».
Для более подробного понимания JVM всегда полезно ознакомиться с ее спецификацией.