1. Обзор
JVM использует два различных метода для инициализации экземпляров объектов и классов.
В этой быстрой статье мы увидим, как компилятор и среда выполнения используют методы <init>
и <clinit>
для целей инициализации.
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 вызывает
специальный метод с именем <init>.
На жаргоне JVM этот метод является методом инициализации экземпляра . Метод является инициализацией экземпляра тогда и только тогда, когда:
- Он определен в классе
- Его имя
<
init>
- Он возвращает
пустоту
Каждый класс может иметь ноль или более методов инициализации экземпляра . Эти методы обычно соответствуют конструкторам в языках программирования на основе JVM, таких как Java или Kotlin.
2.1. Конструкторы и блоки инициализатора экземпляра
Чтобы лучше понять, как компилятор Java переводит конструкторы в <init>
, давайте рассмотрим другой пример:
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 разделены, они находятся в одном и том же методе инициализации экземпляра на уровне байт-кода. Собственно говоря, этот метод <init> :
- Сначала инициализируются поля
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 вызывает другой метод <init>
с сигнатурой, соответствующей конструктору Java.
Ключевым моментом здесь является то, что конструкторы и другие инициализаторы экземпляров эквивалентны методу <init>
в мире JVM.
3. Методы инициализации класса
В Java статические блоки инициализатора полезны, когда мы собираемся инициализировать что-то на уровне класса:
public class Person {
private static final Logger LOGGER = LoggerFactory.getLogger(Person.class); // <clinit>
// <clinit>
static {
System.out.println("Static Initializing...");
}
// omitted
}
Когда мы компилируем предыдущий код, компилятор переводит статический блок в метод инициализации класса на уровне байт-кода.
Проще говоря, метод является инициализирующим класс тогда и только тогда, когда:
- Его имя
<clinit>
- Он возвращает
пустоту
Поэтому единственный способ сгенерировать метод <clinit>
в Java — это использовать статические поля и инициализаторы статических блоков.
JVM вызывает <clinit>
при первом использовании соответствующего класса. Поэтому вызов <clinit>
происходит во время выполнения, и мы не можем увидеть вызов на уровне байт-кода.
4. Вывод
В этой быстрой статье мы увидели разницу между методами <init>
и <clinit>
в JVM. Метод <init>
используется для инициализации экземпляров объектов. Кроме того, JVM при необходимости вызывает метод <clinit>
для инициализации класса.
Чтобы лучше понять, как работает инициализация в JVM, настоятельно рекомендуется прочитать спецификацию JVM .