一、构造函数初始化顺序理论
当一个对象被创建时,初始化是按照下面的顺序完成的。
- 设置成员的值为默认的初始值(0,false,null).
- 调用对象的构造方法(但是还没有执行构造方法体)
- 调用父类的构造方法
- 使用初始化程序的初始块和初始化成员
- 执行构造方法体
验证代码如下:
public class A {
public A() {
System.out.println("A.A called");
}
}
public class B extends A {
public B() {
System.out.println("B.B called");
}
int i=f();
int j;
{
j=37;
System.out.println("initializtion block executed");
}
private int f() {
System.out.println("B.f called");
return 47;
}
}
public class test {
public static void main(String[] args) {
B bobj=new B();
}
}
/*运行结果
A.A called
B.f called
initializtion block executed
B.B called
*/
代码解析:
B的构造方法被调用时,最先做的事情是隐性地调用父类的构造方法。父类必须负责初始化自己的状态而不是等待子类来做。然后,B对象的成员被初始化,这包含一个对B.f的调用和包围在{}中的初始块的执行。最后,B的构造方法体被执行。
构造方法总是默认调用super()方法,除非第1行是以下的内容之一。
- super()
- super(args)
- this()
- this(args)
如果类没有构造方法,一个默认的构造方法(也叫”无参构造方法“)将由Java编译器自动生成。默认构造方法只有在类中没有任何其他的构造方法时才产生。
如果父类中没有默认构造方法,必须明确使用”super(args)调用父类的某个构造方法,以下是一个错误的示范:
class A{
A(int i){}
}
class B extends A{
}
A没有默认的构造方法,但是B的构造方法必须调用A的某个构造方法,因此引发异常。初始化的另一个示例如下:
public class A {
public A() {
System.out.println("A.A called");
}
public A(int i) {
this();
System.out.println("A.A(int) called");
}
}
public class B extends A {
public B() {
this(10);
System.out.println("B.B called");
}
public B(int i) {
super(i);
System.out.println("B.B(int) called");
}
int i=f();
int j;
{
j=37;
System.out.println("initializtion block executed");
}
private int f() {
System.out.println("B.f called");
return 47;
}
}
public class test {
public static void main(String[] args) {
B bobj=new B();
}
}
/*运行结果
A.A called
A.A(int) called
B.f called
initializtion block executed
B.B(int) called
B.B called
*/
这个例子明确使用super()和this()调用,这样就不会进行默认的super()方法调用。this()调用同一个类中的另一个构造方法,这个方法被称为“显示构造方法调用”。示例中,调用顺序如下
- 调用B()构造方法,由于第1行是this(10),所以不调用默认的super()方法,而是调用类中的B(int i)方法
- B(int i)方法第1行是super(i),就是调用了类A的A(int i)方法。
- 类A的A(int i)方法的第1行是this(),调用了类A中的A()构造方法,打印”A.A called”。
- 接着执行A(int i)方法的第2行,打印“A.A(int) called”。
- 然后执行类B的初始化代码块(按变量或{}代码块出现的先后顺序依次初始化),调用f(),打印”B.f called”。
- 执行初始化代码块,打印“initializtion block executed”。
- 执行B(int i)方法后面的代码,打印“B.B(int) called”。
- 执行B()构造方法后面的代码,打印“B.B called”。
二、构造函数初始化顺序实战
public class A {
int a=f();
int f() {
return 1;
}
}
public class B extends A {
int b=a;
int f() {
return 2;
}
}
public class test {
public static void main(String[] args) {
B bobj=new B();
System.out.println(bobj.b); //==2
}
}
表面上看,当初始化完成后,bobj.b的值将是1,毕竟,类B中的b的值是用类A中的a的值初始化的,而a是用f的值初始化的,所以bobj.b的值为1??但结果是2。原因在于B没有构造函数,所以生成一个默认的构造方法,然后它通过调用super()调用A产生的默认构造方法。
A中的成员被初始化,成员变量a被设置为方法f()的值,由于B对象正在被初始化,这里调用的是B对象的f()方法,返回值为2,而不是调用A类中的f()方法。
A产生的构造方法体被执行,然后B的成员被初始化,而b被赋值为a,也就是2.最后,B的构造方法被执行。
将上面的例子稍微修改一下,代码如下:
public class A {
int a=f();
int f() {
return 1;
}
}
public class B extends A {
int b=37;
int f() {
return b;
}
}
public class test {
public static void main(String[] args) {
B bobj=new B();
System.out.println(bobj.a); //==0
System.out.println(bobj.f()); //==37
}
}
可能表面上看输出的两个值bobj.a和bobj.f()应该是一样的,但是正如输出结果所示,他们不一样。
问题在于 a是通过对B的f()方法调用而初始化的,而该方法因被子类B重载而返回变量b的值,但此时变量b还没有被初始化,因此b的初始值为0,即a=0;
PS:在编程中,对象的构造阶段调用可重载的方法是不明智的。
说明:初始化顺序应该是:
父类静态变量>子类静态变量>父类非静态变量>父类静态代码块>父类的构造函数>子类的非静态变量>子类静态代码块>子类构造函数。
`