«1. Обзор
JVM использует два различных метода для инициализации экземпляров объектов и классов.
В этой быстрой статье мы увидим, как компилятор и среда выполнения используют методы \u003cinit\u003e и \u003cclinit\u003e для целей инициализации.
2. Методы инициализации экземпляра
Давайте начнем с простого выделения и назначения объекта:
Object obj = new Object();
Если мы скомпилируем этот фрагмент и посмотрим на его байт-код с помощью javap -c, мы увидим что-то вроде :
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
Для инициализации объекта JVM вызывает специальный метод с именем \u003cinit\u003e. На жаргоне JVM этот метод является методом инициализации экземпляра. Метод является инициализацией экземпляра тогда и только тогда, когда:
-
Он определен в классе Его имя \u003cinit\u003e Он возвращает void
Каждый класс может иметь ноль или более методов инициализации экземпляра. Эти методы обычно соответствуют конструкторам в языках программирования на основе JVM, таких как Java или Kotlin.
2.1. Конструкторы и блоки инициализации экземпляров
Чтобы лучше понять, как компилятор Java транслирует конструкторы в \u003cinit\u003e, рассмотрим другой пример:
public class Person {
private String firstName = "Foo"; // <init>
private String lastName = "Bar"; // <init>
// <init>
{
System.out.println("Initializing...");
}
// <init>
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// <init>
public Person() {
}
}
Это байт-код для этого класса:
public Person(java.lang.String, java.lang.String);
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #7 // String Foo
7: putfield #9 // Field firstName:Ljava/lang/String;
10: aload_0
11: ldc #15 // String Bar
13: putfield #17 // Field lastName:Ljava/lang/String;
16: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #26 // String Initializing...
21: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: aload_0
25: aload_1
26: putfield #9 // Field firstName:Ljava/lang/String;
29: aload_0
30: aload_2
31: putfield #17 // Field lastName:Ljava/lang/String;
34: return
Несмотря на то, что конструктор и блоки инициализатора в Java разделены, они находятся в одном и том же методе инициализации экземпляра на уровне байт-кода. На самом деле, этот метод \u003cinit\u003e:
-
Сначала инициализирует поля firstName и lastName (индексы с 0 по 13) Затем он выводит что-то на консоль как часть блока инициализатора экземпляра (индексы с 16 по 21) И, наконец, он обновляет переменные экземпляра аргументами конструктора
Если мы создадим Person следующим образом:
Person person = new Person("Brian", "Goetz");
Тогда это преобразуется в следующий байт-код:
0: new #7 // class Person
3: dup
4: ldc #9 // String Brian
6: ldc #11 // String Goetz
8: invokespecial #13 // Method Person."<init>":(Ljava/lang/String;Ljava/lang/String;)V
11: astore_1
На этот раз JVM вызывает другой \u003cinit \u003e метод с сигнатурой, соответствующей конструктору Java.
Ключевым выводом здесь является то, что конструкторы и другие инициализаторы экземпляров эквивалентны методу \u003cinit\u003e в мире JVM.
3. Методы инициализации класса
В Java блоки статического инициализатора полезны, когда мы собираемся инициализировать что-то на уровне класса:
public class Person {
private static final Logger LOGGER = LoggerFactory.getLogger(Person.class); // <clinit>
// <clinit>
static {
System.out.println("Static Initializing...");
}
// omitted
}
Когда мы компилируем предыдущий код, компилятор транслирует статический block в метод инициализации класса на уровне байт-кода.
Проще говоря, метод является инициализирующим класс тогда и только тогда, когда:
-
Его имя \u003cclinit\u003e Возвращает void
Следовательно, единственный способ сгенерировать метод \u003cclinit\u003e в Java — это использовать статические поля и инициализаторы статических блоков.
JVM вызывает \u003cclinit\u003e при первом использовании соответствующего класса. Поэтому вызов \u003cclinit\u003e происходит во время выполнения, и мы не можем увидеть вызов на уровне байт-кода.
4. Заключение
В этой быстрой статье мы увидели разницу между методами \u003cinit\u003e и \u003cclinit\u003e в JVM. Метод \u003cinit\u003e используется для инициализации экземпляров объекта. Кроме того, JVM при необходимости вызывает метод \u003cclinit\u003e для инициализации класса.
Чтобы лучше понять, как работает инициализация в JVM, настоятельно рекомендуется прочитать спецификацию JVM.