Java构造方法的初始化顺序

一、构造函数初始化顺序理论

    当一个对象被创建时,初始化是按照下面的顺序完成的。

  1. 设置成员的值为默认的初始值(0,false,null).
  2. 调用对象的构造方法(但是还没有执行构造方法体)
  3. 调用父类的构造方法
  4. 使用初始化程序的初始块和初始化成员
  5. 执行构造方法体

    验证代码如下:

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()调用同一个类中的另一个构造方法,这个方法被称为“显示构造方法调用”。示例中,调用顺序如下

  1. 调用B()构造方法,由于第1行是this(10),所以不调用默认的super()方法,而是调用类中的B(int i)方法
  2. B(int i)方法第1行是super(i),就是调用了类A的A(int i)方法。
  3. 类A的A(int i)方法的第1行是this(),调用了类A中的A()构造方法,打印”A.A called”。
  4. 接着执行A(int i)方法的第2行,打印“A.A(int) called”。
  5. 然后执行类B的初始化代码块(按变量或{}代码块出现的先后顺序依次初始化),调用f(),打印”B.f called”。
  6. 执行初始化代码块,打印“initializtion block executed”。
  7. 执行B(int i)方法后面的代码,打印“B.B(int) called”。
  8. 执行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:在编程中,对象的构造阶段调用可重载的方法是不明智的。
说明:初始化顺序应该是:
    父类静态变量>子类静态变量>父类非静态变量>父类静态代码块>父类的构造函数>子类的非静态变量>子类静态代码块>子类构造函数。

`

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值