当我运行以下代码时:
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();
静态块首次执行 。
答案 0 :(得分:62)
这一切都取决于初始化语句的执行顺序。您的测试表明此订单是:
修改强>
感谢您的评论,现在我可以引用JVM规范中的相应部分。 Here它是详细的初始化过程。
答案 1 :(得分:31)
3 - 是一个静态初始化程序,它在加载类时运行一次,这首先发生。
2 - 是一个初始化块,java编译器实际上会将其复制到每个构造函数中,因此如果您愿意,可以在构造函数之间共享一些初始化。很少使用。
1 - 将在(3)和(2)之后构造对象时执行..
答案 2 :(得分:18)
答案 3 :(得分:5)
Test(){System.out.println("1");}
{System.out.println("2");}
static{System.out.println("3");}
首先执行静态事务,{System.out.println("2");}
不是函数的一部分,因为它的作用域首先被调用,而Test(){System.out.println("1");}
最后被调用,因为其他两个被称为第一个1} p>
答案 4 :(得分:5)
首先,将类加载到JVM中并进行类初始化。在此步骤中,执行静态块。 “{...}”只是“static {...}”的句法等价物。由于代码中已经存在“静态{...}”块,因此将附加“{...}”。这就是为什么你在2之前打印3。
接下来,一旦加载了类,java.exe(我假设你从命令行执行)将找到并运行main方法。 main静态方法初始化其构造函数被调用的实例,因此最后打印“1”。
答案 5 :(得分:4)
因为在JVM中首次初始化类时(即使在调用static{}
之前)运行main()
代码,所以在首次初始化实例时会调用实例{}
在构造之前,然后在完成所有操作之后调用构造函数。
答案 6 :(得分:4)
我已经通过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”中,这是一个类初始值设定项。
对象的生命周期通常是:loading class - &gt;链接类 - &gt;类初始化 - &gt;对象实例化 - &gt;使用 - &gt; GC 。这就是为什么3首先出现的原因。因为这是在类级别而不是对象级别,所以它将出现一次,因为类类型将被加载一次。有关详细信息,请参阅inside the Java2 Virtual Machine : life time of a type
LDC "2"
和`LDC "1"
位于构造函数“init”中。
它按此顺序排序的原因是:构造函数将首先在类的{}中执行一些implict指令,如超级构造函数和代码,然后执行其construtor中明确的代码。
这就是编译器对java文件的作用。
答案 7 :(得分:4)
执行顺序如,
- 静态阻止
- 实例块
- 构造
醇>
<强>解释强>
无论何时以任何方式访问类,静态块始终只会被一次调用,在您运行程序的情况下。 (这就是静态块的意思)。它不依赖于实例,因此在创建新实例时不会再次调用它。
然后实例初始化块将为每个创建的实例调用,然后创建每个实例的构造函数。因为它们都可以用于实例化实例。
编译后代码将成为
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编译器将初始化程序块复制到每个构造函数中。因此,这种方法可用于在多个构造函数之间共享代码块。
答案 8 :(得分:4)
看起来没有人说明为什么 3 只是明确打印一次。所以我想补充一点,这与它首先打印的原因有关。
静态定义的代码被标记为与该类的任何特定实例分开。通常,静态定义的代码可以被认为根本不是任何类(当然,在考虑作用域时,该语句中存在一些无效性)。因此,一旦加载了类,代码就会运行,如上所述,因为在构造实例Test()
时不是被调用,因此多次调用构造函数将不会导致静态代码再次运行。
包含 2 的括号中的代码前置于构造中,如上所述,因为它是类中所有构造函数的先决条件。您不知道测试的构造函数会发生什么,但您可以保证它们都以打印 2 开始。因此,这发生在任何特定构造函数中的任何内容之前,并且每次调用(ny)构造函数时都会调用它。