当我运行以下代码时:
public class Test {
Test(){
System.out.println("1");
}
{
System.out.println("2");
}
static {
System.out.println("3");
}
public static void main(String args[]) {
new Test();
}
}
我希望按以下顺序输出:
1
2
3
但我得到的是相反的顺序:
3
2
1
有人能解释一下为什么它以相反的顺序输出吗?
====================
此外,当我创建多个Test
:实例时
new Test();
new Test();
new Test();
new Test();
静态块在第一时间仅执行。
这一切都取决于初始化语句的执行顺序。您的测试表明此订单为:
- 静态初始化块
- 实例初始化块
- 施工人员
编辑
感谢您的评论,现在我可以引用JVM规范中适当的部分了。这里是详细的初始化过程。
3-是一个静态初始值设定项,它在加载类时运行一次,这首先发生。
2-是一个初始化器块,java编译器实际上会将其复制到每个构造函数中,因此如果您愿意,可以在构造函数之间共享一些初始化。很少使用。
1-将在构造对象时执行,在(3(和(2(之后。。
这里的更多信息
首先执行静态块。
然后实例实例初始化阻止
请参阅JLS,例如初始化程序
{
//sop语句
}
实例初始化块中不能有返回语句,就像构造函数一样。
Test(){System.out.println("1");}
{System.out.println("2");}
static{System.out.println("3");}
静态的东西首先被执行,{System.out.println("2");}
不是函数的一部分,因为它的作用域,它被首先调用,而Test(){System.out.println("1");}
被最后调用,因为其他两个被称为第一个
首先,类被加载到JVM中,类初始化发生。在此步骤中,执行静态块。"{…}"只是"static{…}"的语法等价物。由于代码中已经有一个"static{…}"块,"{..}"将被附加到它后面。这就是为什么在2之前打印了3。
接下来,一旦加载了类,java.exe(我假设您是从命令行执行的(将找到并运行主方法。主静态方法初始化其构造函数被调用的实例,因此最后打印的是"1"。
因为static{}
代码是在类首次在JVM中初始化时运行的(即,甚至在调用main()
之前(,所以在实例首次初始化时,在构造实例之前,会调用实例{}
,然后在完成所有这些之后调用构造函数。
我在这里通过ASM获得了类似字节码的代码。
我认为这可以回答你的问题,解释在这种情况下创建对象时发生了什么。
public class Test {
static <clinit>() : void
GETSTATIC System.out : PrintStream
LDC "3"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN
<init>() : void
ALOAD 0: this
INVOKESPECIAL Object.<init>() : void
GETSTATIC System.out : PrintStream
LDC "2"
INVOKEVIRTUAL PrintStream.println(String) : void
GETSTATIC System.out : PrintStream
LDC "1"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN
public static main(String[]) : void
NEW Test
INVOKESPECIAL Test.<init>() : void
RETURN
}
我们可以看到LDC "3"
在"clinit"中,这是一个类初始化器。
对象的生存期通常是:加载类->链接类->类初始化->对象实例化->使用->GC。这就是为什么3首先出现的原因。由于这是在类级别,而不是对象级别,它将显示一次,因为类类型将加载一次。有关详细信息,请参阅Java2虚拟机内部:类型的寿命
LDC "2"
和`LDC "1"
在构造函数"init"中。
按此顺序排列的原因是:构造函数将首先执行一些隐含指令,如类的{}中的超级构造函数和代码,然后执行其构造函数显式中的代码。
这就是编译器将对java文件执行的操作。
似乎没有人明确说明为什么3只打印一次。所以我想补充一点,这与为什么它首先被打印有关。
静态定义的代码被标记为与类的任何特定实例分离。通常,静态定义的代码可以被认为根本不是任何类(当然,当考虑范围时,该语句中存在一些无效性(。因此,一旦加载类,代码就会运行,如上所述,在中,当构造实例Test()
时,它不会被调用,因此多次调用构造函数将不会导致静态代码继续运行。
如上所述,包含2的带括号的代码被预先添加到构造中,因为它在某种程度上是类中所有构造函数的先决条件。您不知道Test的构造函数中会发生什么,但可以保证它们都是从打印2开始的。因此,这发生在任何特定构造函数中的任何事情之前,并且在每次调用(ny(构造函数时都会被调用。
完整说明
执行顺序类似于
- 静态闭塞
- 实例块
- 构造函数
解释
静态块在一开始总是只调用一次,无论何时通过任何方式访问类,在您运行程序的情况下。(这就是静态块的作用(。它不依赖于实例,因此在创建新实例时不会再次调用。
然后,实例初始化块将为创建的每个实例调用,然后为创建的每一个实例调用构造函数。因为它们都可以用于实例化实例。
实例初始化块实际上是在构造函数之前调用的吗
编译后代码将变为,
public class Test {
Test(){
super();
System.out.println("2");
System.out.println("1");
}
static {
System.out.println("3");
}
public static void main(String args[]) {
new Test();
}
}
您可以看到,在实例块中编写的语句本身就成为构造函数的一部分。因此,它是在构造函数中已经编写的语句之前执行的。
根据本文件
Java编译器将初始值设定项块复制到每个构造函数中。因此,这种方法可以用于在多个构造函数之间共享一个代码块。