先给出一个类的样式:
class Template {
int i;//字段声明
double d;
String name= "a name";//初始化语句(字段声明并赋值)
//这是一个初始化块
{
d = 1.0;
}
Template(){
name = "another name";
}
}
创建一个对象时,对象的所有字段(属性成员,也就是类变量)会被赋予一个默认值,如上面的Template类,就算字段i没有给出初始值,也是合法的。当创建Template对象时,i会被赋予默认的初始值0。正如前面所说的,所有字段都会被赋予默认值,所以即使name字段被赋予了初始值,在name被赋予初始值前,首先被赋予了默认值null;
有三种方式可以在构造对象时初始化其字段:初始化语句(即声明字段是给其赋值),初始化块,构造函数。
初始化块的地位和初始化语句的地位差不多,初始化块的出现是为了满足复杂的初始化语的需求(可以像方法一样在初始化块里写复杂的语句)。
执行字段初始化的顺序可以简单地看成:给所有字段赋初始值,然后调用初始化语句,然后调用初始化块,最后调用构造函数。
以下是我测试的代码:
public class A{
String s = "A:statement";
{
System.out.println(s);
s = "A:block";
System.out.println(s);
}
A(){
s = "A:constructor";
System.out.println(s);
}
public static void main(String[] args){
new A();
}
}
尝试运行上面的代码,可以看出初始化时执行的顺序,初始化语句先于初始化块先于构造函数(未体现赋默认值).
但是,面向对象模型是有继承层次的,考虑到继承,就要复杂一点了。
字段的初始化分为两个阶段,第一阶段是给所有的字段(包括继承而来的字段)赋默认值,然后进入第二阶段。第二阶段分为3个步骤:
1.调用其父类的构造器
2.调用这些字段的初始化语句和初始化块
3.执行构造体
上面这段话取自书本,有点难理解,我个人认为表述不准确。
下面以我自己的理解用一个模型来解释初始化的第二阶段的顺序。
类{
初始化语句;
{
初始化块
}
构造函数(){
super(..);//对应第二阶段的步骤1
*调用初始化语句//星号(*)表示实际代码不存在于此处,只是在此时执行
*调用初始化块
执行构造函数体//对应第二阶段步骤3
}
构造函数2(){
this(..);
执行构造函数体
}
}
以下代码是我自己编写以便于理解,你们可以运行输出结果试试。
class A{
String s = "A:statement";
{
System.out.println(s);
s = "A:block";
System.out.println(s);
}
A(){
s = "A:constructor";
System.out.println(s);
}
}
class B extends A{
String s = "B:statement";
{
System.out.println(s);
s = "B:block";
System.out.println(s);
}
B(){
s = "B:constructor";
System.out.println(s);
}
}
class C extends B{
String s = "C:statement";
{
System.out.println(s);
s = "C:block";
System.out.println(s);
}
C(){
s = "C:constructor";
System.out.println(s);
}
}
public class ConstructTest{
public static void main(String[] args){
new C();
}
}
上面的模型表达的初始化过程类似于递归调用,导致的结果是最底层的父类首先进行初始化,然后向上一级,一次类推。所以可以总结出以下结论:
实例化一个类的时候,由继承关系从低高(父类到子类)的顺序初始化字段。