«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 выполняет следующие операции:

  1. First, it finds a place in its process space for the new object.
  2. 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.
  3. 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 всегда полезно ознакомиться с ее спецификацией.