
当使用new创建一个 Java 对象的时候,其初始化过程可以分为:
- 类的初始化:静态变量、静态初始化块
- 对象的初始化:普通变量、初始化块、构造器
1.类的初始化顺序
Java类的初始化执行顺序可以大致归纳为:静态变量、静态初始化块>普通变量、初始化块>构造器。
以static修饰的静态变量和静态初始化块是类相关的,无论创建多少对象,这些静态域只在第一次遇到这个类的时候初始化一次。看下面的代码
public class {
public static void main(String[] args) {
new StaticClass();
new StaticClass();
}
}
class StaticClass {
static Echo e1 = new Echo("e1");
static {
System.out.println("static field");
}
static Echo e2 = new Echo("e2");
Echo e3 = new Echo("e3");
{
System.out.println("common field");
}
public StaticClass() {
System.out.println("constructor");
}
}
class Echo {
Echo(String s) {
System.out.println(s);
}
}
输出结果为:
e1
static field
e2
e3
common field
constructor
e3
common field
constructor
静态变量e1 e2和静态块最先执行,且只执行了一次,而普通变量、初始化块、构造器则会在每次new对象的时候执行。静态变量和静态初始化块之间按代码顺序执行。
2.基于继承的类的初始化顺序
对于具有继承关系的类,其初始化过程可以由下面的代码验证
class Parent {
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
}
}
public class SubClass extends Parent {
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
}
// 程序入口
public static void main(String[] args) {
new SubClass();
}
}
输出结果为:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
因此,类的初始化顺序:父类–静态变量>父类–静态初始化块>子类–静态变量>子类–静态初始化块 >父类–变量>父类–初始化块>父类–构造器>子类–变量>子类–初始化块>子类–构造器。
3.总结与实践
Java 类的初始化顺序:
规则1:在类第一次加载的时候,将会进行静态域的初始化:
-
将所有的静态数据域初始化为默认值(0、false 和 null)
-
按照在类中定义的顺序依次执行静态初始化语句和静态初始化块
规则2:调用构造器的具体处理步骤:
-
将所有的数据域初始化为默认值(0、false 和 null)
-
按照在类中定义的顺序依次执行初始化语句和初始化块
-
如果构造器调用的其他的构造器,则转而执行另一构造器
-
执行构造器主体
再看下面的例子
public class Test {
public static int k = 0;
public static Test t1 = new Test("t1");
public static Test t2 = new Test("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
{
print("构造块");
}
static {
print("静态块");
}
public Test(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
Test t = new Test("init");
}
}
我们依照上面的准则来分析这个程序的输出(空格忽略):
1.首先程序会调用main函数,在生成Test对象前会先初始化Test的静态域。在进行静态域初始化的过程中,会先将数据静态数据域初始化为默认值(0、false 和 null),然后按照在类中定义的顺序依次执行静态初始化语句和静态初始化块。因此在执行第一句静态赋值语句前,静态变量的状态如下:
k = 0;
t1 = null;
t2 = null;
i = 0;
n = 0;
2.变量k被赋值为0,变量t1的赋值语句则会调用构造函数,根据上面的规则,调用构造函数之前要先初始化非静态数据域初始化为默认值并按照在类中定义的顺序依次执行初始化语句和初始化块。因此程序接下来会执行
private int a = 0;
public int j = print("j");
{
print("构造块");
}
由于此时i和n的静态赋值语句尚未执行到,因此仍未默认值,得到输出
1:j i=0 n =0
2:构造块 i=1 n=1
接下来执行构造函数,得到输出
3:t1 i=2 n=2
3.接下来再执行t2的赋值语句,和上面的步骤类似,得到输出
4:j i=3 n =3
5:构造块 i=4 n=4
6:t2 i=5 n=5
4.然后执行i的赋值语句,得到输出
7:i i=6 n=6
执行完毕i的值为7,然后n赋值为99。执行静态块,得到输出
8:静态块 i=7 n=99
5.静态域执行完毕,在为main函数里的变量t执行构造函数前,依然要先初始化执行非静态域,此时的静态变量的值为:
k = 8
i = 8
n = 100
得到输出为:
9:j i = 8 n=100
10:构造块 i=9 n=101
再执行构造函数,得到的输出为
11:init i=10 n=102
综上所述,整个程序的输出为
1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
7:i i=6 n=6
8:静态块 i=7 n=99
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102
其实这个例子看起来好像在执行某些静态初始化语句前执行了非静态初始化语句,但这是因为在静态初始化语句里面调用了构造函数,整体上仍然符合“静态变量、静态初始化块>普通变量、初始化块>构造器”的顺序。
参考文章:
http://blog.csdn.net/geekdonie/article/details/12260599
http://my.oschina.net/haquanwen/blog/96810
Thinking In Java 4th Edition




近期评论