这道题来自 stackoverflow。
问题描述:
有如下代码,求其输出内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class Test
{
public int a = 10;
Test(){System.out.println("1");}
{System.out.println("2");}
static{System.out.println("3");}
public static void main(String args[])
{
new Test();
}
}
|
分析
作为静态区段的语句,容易知道,3 是会最先出现的。容易弄错的一点是 1 和 2 的出现顺序。
我们可以参考字节码来分析。在 eclipse 中使用 ASM bytecode 插件,得到相应的字节码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // class version 50.0 (50)
// access flags 0x21
public class Test {
// compiled from: Test.java
static <clinit>() : void
GETSTATIC System.out : PrintStream
LDC "3"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN
<init>() : void
ALOAD 0: this
INVOKESPECIAL Object.<init>() : void
ALOAD 0: this
BIPUSH 10
PUTFIELD Test.a : int
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
}
|
正如我们所想,3 是被放在类构造方法中,这是类的初始化函数,固然在类的初始化时出现。
而在构造方法中先出现 2,之后才是 1。问题的核心集中到对象构造方法的指令顺序问题。实际上,在对象构造方法中,会先执行一些隐性的指令,比如父类的构造方法、{}区段的内容等,然后再执行显性的构造方法中的指令:
- Java 编译时,对象构造方法里先嵌入隐式的指令,完毕之后,再执行 Java 源代码中显示的代码。
- 那些隐式的指令,包括父类的构造方法、变量的初始化、{}区段里的内容,并严格按照这个顺序嵌入到对象的构造方法中。
- {}区段里的内容和变量的初始化语句的执行顺序,依据源代码中本身的顺序执行。
相关文章参见:《Java 类、实例的初始化顺序》