Инициализация значения экземпляра по умолчанию в java
Я пытаюсь проверить значение по умолчанию для переменных экземпляра (т.е. 0 здесь) в сгенерированном байт-коде . я вижу
<init>()
вызывается, и если я печатаю переменную экземпляра внутри конструктора, я вижу
getfield
требовалось, но тогда где сначала было установлено это значение по умолчанию?
Пожалуйста, ответьте на следующие вопросы:
- Когда устанавливается значение по умолчанию в
myvar
? (по истечении времени компиляции или создания объекта) - Кто (компилятор или jvm) инициализирует (или, скажем, устанавливает значение по умолчанию) в переменной экземпляра?
public class FieldInit {
int myvar;
public static void main(String[] args) {
new FieldInit(); // and what would happen if I comment out this
}
}
Я пытаюсь разобрать байт-код, используя
javap
но не могу увидеть метод, я предполагаю, что это может происходить здесь. Пожалуйста, дайте мне знать, можно ли увидеть
<clinit>()
метод, и если да, то как?
2 ответа
В JVM создание экземпляра объекта разделено на две инструкции байт-кода:
- выделяет новый неинициализированный объект;
-
invokespecial
вызывает конструктор, который инициализирует объект.
Спецификация JVM для байт-кода гласит :
Память для нового экземпляра этого класса выделяется из кучи со сборкой мусора, а переменные экземпляра нового объекта инициализируются начальными значениями по умолчанию.
JVM устанавливает все поля экземпляра в ноль при выполнении
new
инструкция. Итак, к моменту вызова конструктора для всех полей уже установлены значения по умолчанию. Вы не найдете этого «обнуления» в байт-коде - оно выполняется JVM неявно во время выделения объекта.
- На ваш вопрос невозможно ответить, потому что он сложнее, чем вы думаете.
поля в java кодируются в структуре данных поля, и эта структура данных включает место для начального значения. Однако единственными возможными начальными значениями в этой структуре данных являются числа и строки .
Посмотрим на это в действии! (и обратите внимание, что
L
это просто синтаксис java, чтобы сообщить java: это число является
long
, а не
int
. 5 - постоянная величина, как и 5L).
class Test {
public static final long mark = 5L;
}
javac Test.java
javap -v c Test
..... public static final long mark; descriptor: J flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: long 5l
Послушайте, вот оно, постоянная стоимость 5 л.
Но что, если он не постоянный?
Ах, это проблема. Вы не можете закодировать это здесь.
Итак, вместо этого пришло время синтаксического сахара!
Вы можете написать специальный метод в любом классе, который запускается во время статической инициализации класса. Вы также можете написать специальный метод, который запускается при создании нового экземпляра. Он почти полностью совпадает с конструктором, только с экзотическими отличиями. Это выглядит так:
public class Test {
static {
System.out.println("What voodoo magic is this?");
}
public static void main(String[] args) {
System.out.println("In main");
}
}
Посмотрим на это в действии!
javac Test.java java Test What voodoo magic is this? In main javap -c -v Test ... static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 0: getstatic #7 // Field >java/lang/System.out:Ljava/io/PrintStream; 3: ldc #21 // String Voodoo... 5: invokevirtual #15 // Method >java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 }
Как видите, эта странная штука была скомпилирована во что-то, что выглядит в точности как метод, байт-код и все такое, но название метода странное, просто
static{}
.
Вот и подсказка
Но что произойдет, если мы немного усложним это. Вместо этого давайте инициализируем это поле текущим временем!
class Test {
public static final long mark = System.currentTimeMillis();
}
Это просто синтаксический сахар. Это объясняет, как это работает, потому что, как я уже говорил, на уровне файла класса вы не можете инициализировать поля неконстантными . Таким образом, это компилируется в то же самое, что и:
class Test {
public static final long mark;
static {
mark = System.currentTimeMillis();
}
}
и ты можешь
javap
чтобы подтвердить это.
Один из них называется «постоянной времени компиляции». Другой - нет. Это проявляется по-разному. Например, вы можете передать CTC в качестве параметра аннотации. Попробуйте это: попробуйте использовать
static final long MARK = 5L;
(вы сможете), тогда
static final long MARK = System.currentTimeMillis();
- этого не допустят.
Итак, где это начальное значение? Если это постоянное значение,
javap -c -v
покажет это. Если это не так, он застрял в статическом блоке.