这个太重要了。
首先要注意,初始化包括对象的初始化和类的初始化不一样。
所有过程用一个例子说明,但在最开始有一个问题:对象是由构造器(new XX())创建的吗?
不是!构造器之前就已经为对象分配内存了,也就是它已经被创建了,那么,初始值是什么呢?就是系统默认值,无非三种值:0,NULL,false。除非变量用final修饰过,系统就不会为它赋初值,这也正是用final修饰的变量必须要显示地赋初值。
还有另一点也要注意的:虽然我们通常把类和对象区别对待,但实际上,类和对象都是对象,类也是对象,都是Class类的实例。
如ClassA类的实例对象objectA、objectB、objectC……和Class类间的关系如下:
static变量属于这里只占一份内存间 |
由此可以看见,类与对象并不存在本质上的区别,只是设计问题而已。
以下代码部分摘自http://rdc.taobao.com/team/jm/archives/331
public class A extends B{
public final int c=12;
public A(){ //①跳到B的构造函数
System.out.println(a);
a=200;
}
public static int b=10; //初始化静态变量
public int a=50;
{a=100;}// ②普通初始化块
public static void main(String args[]){
System.out.println(new A().a);
}
}
class B{
public B(){
System.out.println(this.getClass());
System.out.println(((A)this).a); //③
}
}
运行结果是:
class A
0
100
200
对照这个输出,我们来详细分析一下对象的初始化顺序:
1、 以默认值为变量初始化,以及初始化final变量的值:
2、 从子类到父类,由子类逐层地往上调用父类构造函数,所以先初始化父类后才对子类地进行初始化。所以当运行到语句①时,程序跳到B的构造函数,由A调用B的构造函数。由此类推,一直跳到Object类,执行Object的构造器。最后才初始化本类对象。
对于上述第二点的说明:
其实没有必要把实例变量和类变量分开看,它们都一样,即初始化有三种:定义时初始化、初始化块、构造函数初始化(只是static变量不存在于构造函数中)。
对于实例变量,编译器处理三种初始化的方法是:把三种方式都合并在一起,其中的顺序是,定义时初始化与初始化块的初始化的顺序与它们在源程序中的位置一致,而这两种方式一定优先于构造器的初始化。
对于类变量,编译器处理两种初始化的方法是:把三种方式都合并在一起,其中的顺序是,定义时初始化与初始化块的初始化的顺序与它们在源程序中的位置一致(与实例变量一样)。但,不一样的是,类变量在程序运行过程中只初始化一次,发生在类的初始化过程,即在对象初始化之前。所以,在这里并不涉及静态变量的初始化。
例如语句①到语句②之间的所有代码,可以看到源程序中,构造器的位置在定义时初始化和初始化块前面。但编译器的把它们合并在一起的时候的处理是:
public int a=50;
{a=100;}//②普通初始化块,到此可以看出,初始化块和定义变量与它们在源程序中的位置一致。
System.out.println(a); //构造器里的内容,在三种方式中的最后。
a=200;
1
,为A类分配内存空间,初始化所有成员变量为默认值,包括primitive类型(int=0,boolean=false,…)和Reference类型。为c赋值12。
2,调用A类构造函数。
3,调用B类构造函数。
4,调用Object空构造函数。(java编译器会默认加此构造函数,且object构造函数是个空函数,所以立即返回)
5,初始化B类成员变量,因为B类没有成员变量,跳过。
6,执行语句③sysout输出子类A的成员变量小a。// 此时为0
7,初始化A类成员变量,将A类成员变量小a赋值100。
8,执行sysout输出当前A类的成员变量小a。// 此时为100
9,赋值当前A类的成员变量小a为200。
10,main函数中执行sysout,输出A类实例的成员变量小a。// 此时为200
代码和解说部分借鉴于http://rdc.taobao.com/team/jm/archives/331