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
|
public
class
StaticTest {
public
static
int
k =
0
;
public
static
StaticTest s1 =
new
StaticTest(
"s1"
);
public
static
StaticTest s2 =
new
StaticTest(
"s2"
);
public
static
int
i = print(
"i"
);
public
static
int
n =
99
;
public
int
j = print(
"k"
);
{
print(
"构造块"
);
}
static
{
print(
"静态块"
);
}
public
static
int
print(
String
s) {
System.out.println(++k +
":"
+ s +
"\ti="
+ i +
"\tn="
+ n);
++n;
return
++i;
}
public
StaticTest(
String
s) {
System.out.println(++k +
":"
+ s +
"\ti="
+ i +
"\tn="
+ n);
++i;
++n;
}
public
static
void
main(
String
[] args) {
new
StaticTest(
"init"
);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
-----------------运行结果------
1
:k i=
0
n=
0
2
:构造块 i=
1
n=
1
3
:s1 i=
2
n=
2
4
:k i=
3
n=
3
5
:构造块 i=
4
n=
4
6
:s2 i=
5
n=
5
7
:i i=
6
n=
6
8
:静态块 i=
7
n=
99
9
:k i=
8
n=
100
10
:构造块 i=
9
n=
101
11
:init i=
10
n=
102
|
原理分析:
/*
* 没想到只是创建了一个对象,居然执行了这么多语句!下面我们逐条分析每条输出语句。
*
* 首先我们需要对java程序的加载过程有个大概的了解:
* 第一执行类中的静态代码,包括静态成员变量的初始化和静态语句块的执行;
* 第二执行类中的非静态代码,包括非静态成员变量的初始化和非静态语句块的执行,
* 最后执行构造函数。
* 在继承的情况下,会首先执行父类的静态代码,然后执行子类的静态代码;
* 之后执行父类的非静态代码和构造函数;最后执行子类的非静态代码和构造函数。用图表示如下:
*
* 第一条语句打印的是j相关的内容,所以执行了第7行代码。很明显该行代码执行的是非静态变量的赋值操作,这似乎不符合上述java程序加载规则。
* 我们按照前述规则执行一下代码,
* 首先会执行静态变量k的赋值,然后创建该类的一个静态实例。这时我们就会发现,第一条打印语句可能和该类的这个静态实例对象有关。我们尝试着创建这个静态实例,
* 这时的程序加载过程又变为上述标准的加载过程:首先执行静态代码,然后非静态,最后构造函数。由于静态代码的执行是按代码的先后顺序进行,
* 所以创建该静态实例时只有第一个静态变量k会赋值,后面的静态变量和静态语句块还都不存在;之后执行非静态代码,第一句非静态代码即是代码第7行的变量j赋值。
* 这就解释了为什么第一条打印语句会是第7行的代码。同时这也解释了第二和第三条打印,第二条打印语句执行非静态代码,执行之后就调用构造函数创建实例对象s1。
*
* 同理,第4到6条打印语句是在创建静态实例对象s2时执行的。
*
* 在完成两个静态实例对象的创建后,下面要执行静态变量i的赋值,这就是第7条打印语句。后面还会对静态成员变量n赋值。之后是执行静态语句块,打印第8行语句。
* 执行完静态代码部分后,接下来要执行非静态代码部分,按照写代码的前后顺序先为j赋值,然后执行非静态语句块,这就是第9和10行的打印语句。
* 在执行完上面的所有步骤之后,开始执行类的构造函数创建对象,这就是第11行打印语句。
*
* 通过上面的分析我们发现:上述代码的执行顺序依旧符合一开始说明的java程序加载过程。只是由于有两个该类的静态实例变量,导致打印语句的复杂化。在这种情况下,
* 打印过程类似于一个递归, 每一次递归都按照标准的加载过程执行。
*
* 该代码一开始给人很多疑问,感觉运行过程中会抛出各种异常,但是代码却神奇地打印出了11条语句,的确让人吃惊。第一个疑问是该类内部有一个该类自身的静态对象,
* 是否会导致循环递归。大家可以尝试一下,将代码第3或4行的static去掉,然后运行程序,就会提示StackOverflowError异常。为啥静态对象不会导致栈溢出,
* 而非静态对象就会溢出?这是因为静态成员变量属于类所有,所有的类对象共享该静态成员变量,也即该静态成员变量只有一份,所以在递归的过程中,当发现正在创建该静态变量时,
* 系统不会再去创建该变量,所以不会递归。但是如果是非静态对象,在递归的过程中,每次遇到该new语句都会再次创建一个新的对象,导致栈溢出。
*
* 该代码中另一个疑问是变量i的初值。在静态函数print中会打印i的值,但是在打印的时候变量i可能还没有定义(前6行打印语句都没有定义变量i)。
* 程序居然也给通过了,这说明静态变量的声明会在初始化之前完成,并赋初值0。
*/
本文转自arac 51CTO博客,原文链接:http://blog.51cto.com/skyarac/1347338,如需转载请自行联系原作者