美文网首页程序员
为什么Java实例化子类时会递归调用父类构造方法

为什么Java实例化子类时会递归调用父类构造方法

作者: 谢随安 | 来源:发表于2017-10-15 00:59 被阅读137次

下面的代码输出结果会是什么?

public class A {
    double a;
    double b;
    A(){
        a = -1;
        b = -1;
    }
}

class B extends A{
    private double c;

    B(){
        c = -1;
    }
}

class Demo{
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        System.out.println(b.a);
    }
}

答案是 -1.0。

原因在于当实例化子类时会递归调用父类中的构造方法。而这又是为什么呢?


首先,这涉及到Java对象的生命周期。在Java中,对象的生命周期包括以下几个阶段:

  1. 创建阶段(Created)
  2. 应用阶段(In Use)
  3. 不可见阶段(Invisible)
  4. 不可达阶段(Unreachable)
  5. 收集阶段(Collected)
  6. 终结阶段(Finalized)
  7. 对象空间重分配阶段(De-allocated)

1. 创建阶段(Created)

在创建阶段系统通过下面的几个步骤来完成对象的创建过程:

  1. 为对象分配存储空间
  2. 开始构造对象
  3. 从超类到子类对static成员进行初始化
  4. 超类成员变量按顺序初始化,递归调用超类的构造方法
  5. 子类成员变量按顺序初始化,调用子类构造方法。

一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段。

2. 应用阶段(In Use)

对象至少被一个强引用持有着。

3. 不可见阶段(Invisible)

当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。
简单说就是程序的执行已经超出了该对象的作用域了。

4. 不可达阶段(Unreachable)

对象处于不可达阶段是指该对象不再被任何强引用所持有。
与“不可达阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。

5. 收集阶段(Collected)

当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。
没有特殊原因的话不要重载finazlie()方法,原因有两点:

  1. 会影响JVM的对象分配与回收速度:
    在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后才会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC。
  2. 可能造成该对象的再次“复活”:
    在finalize()方法中,如果有其它的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又重新变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用后续的代码管理。

6. 终结阶段

当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。

7.对象空间重新分配阶段

垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。

关于垃圾回收机制可见Java 垃圾回收机制


因此当实例化子类时会默认隐式递归调用父类中的构造方法。文章最开始的代码相当于:

public class A {
    double a;
    double b;
    A(){
        a = -1;
        b = -1;
    }
}

class B extends A{
    private double c;

    B(){
        super();
        c = -1;
    }
}

class Demo{
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        System.out.println(b.a);
    }
}

参考资料

Java对象的生命周期

相关文章

  • Java 构造方法中的多态

    类的实例化顺序是先实例化父类在实例化子类,也就是先调用父类的构造方法,再调用子类的构造方法,如果在构造方法中调用其...

  • 为什么Java实例化子类时会递归调用父类构造方法

    下面的代码输出结果会是什么? 答案是 -1.0。 原因在于当实例化子类时会递归调用父类中的构造方法。而这又是为什么...

  • Java面向对象

    1、子类实例化时会默认调用父类无参构造函数,如果父类没有无参构造函数,则需要子类构造函数显示调用父类有参构造函数 ...

  • 5,super

    实例化一个类,会自动调用这个类的父类的构造方法 super调用父类的构造方法通常是有参的构造方法,参数和子类的构造...

  • 2_9.super关键字

    1.子类实例化的过程中父类的构造器先被调用,然后在调用子类的构造器 2.子类通过构造器实例化时要调用父类的默认构造...

  • 第十五天学习Java(面向对象的多态性)

    2.1子类对象的创建过程 实例化子类对象的过程 调用子类的构造方法中,先调用了父类的构造方法,默认调用的父类的无参...

  • 子类继承父类函数

    如果在子类中需要父类的构造方法就需要显式地调用父类的构造方法,或者不重写父类的构造方法。子类不重写 init,实例...

  • Java中父子类的那些事

    Java中父子类的那些事 构造方法 1、子类构造方法执行时,都会先执行父类的构造方法,若没有显式地调用父类的构造方...

  • Java类中不做事且没有参数的构造方法的作用

    Java在执行子类的构造方法时,假如没有用super()来调用父类特定的构造方法,则会默认调用父类中没有参数的构造...

  • 在 Java 中定义一个不做事且没有参数的构造方法的作用

    Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有...

网友评论

    本文标题:为什么Java实例化子类时会递归调用父类构造方法

    本文链接:https://www.haomeiwen.com/subject/jdkquxtx.html