我正在尝试检查生成的字节码中实例变量的默认值(即此处为0(。我可以看到<init>()
被调用,如果我在构造函数中打印myvar
实例变量,那么我会看到getfield
被调用用于myvar
,但这个默认值首先设置在哪里?
请回答以下问题:
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>()
方法,我想这可能会发生。请让我知道是否可以看到<clinit>()
方法,如果可以,如何?
在JVM中,对象实例化被拆分为两个字节码指令:
new
分配一个新的未初始化对象invokespecial
调用一个初始化对象的构造函数
new
字节码的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
嘿,看,它在那里,常数值5L。
但如果它不是恒定的呢?
啊,这是个问题。你不能在这里编码。
所以,相反,这是语法糖时间!
您可以在类的静态初始化期间运行的任何类中编写一个特殊方法。您还可以编写一个特殊的方法,该方法在创建新实例时运行。这一点几乎与构造函数完全相同,只有奇异的差异。它看起来像这样:
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{}
东西被编译成了一个看起来完全像方法、字节码等等的东西,但方法的名称很奇怪,它只是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
会显示它。如果它不是,它就卡在静态块中。