java基础小细节02:new对象时父子类中在发生什么

58 阅读5分钟

在Java基础小细节01中我有说到在创建一个对象时,在被创建的类中代码的执行顺序:

  1. 调用静态代码块和静态属性初始化;
  2. 调用普通代码块和普通属性初始化;
  3. 调用构造方法;

在这一篇文章我们再来从这一点开始向继承和new对象时父子类代码执行顺序深入。
首先我先来提一下上一篇文章漏掉的几个知识点。
一. 类是什么时候被加载?

  1. 创建对象实例时(new 类名();)
  2. 创建子类对象实例,父类也会被加载
  3. 使用类的静态成员

二. static代码块是对类类进行初始化,而且它随着类的加载而执行,并且只会执行一次。

三. 普通代码块在创建对象实例时会被隐式调用,对象被创建一次代码块就会被调用一次,如果使用类的静态成员时, 普通代码块并不会执行。

这几个知识点都很简单,记住就行不理解也没关系下面我会用代码一步一步的体现出来。
首先在上一篇文章我我已经说明了在创建对象时对象类中的代码执行顺序,但是那一个对象类并没有手动的给他添加上一个父类所以无法体现出在new对象时父子类代码执行顺序。这次我们来给他升级一下,上代码!

public class  Test{
    public static void main(String[] args) {
        Som som = new Som(10);
    }
}

class Father{
    static  int a = 10;
    static{
        System.out.println("父类静态代码块");
    }
    int A = 5;
    {
        System.out.println("父类普通代码块");
    }
    public Father(){
        System.out.println("父类无参构造器");
    }
}

class Som extends Father{
    static  int a = 10;
    static{
        System.out.println("子类静态代码块");
    }
    int A = 5;
    {
        System.out.println("子类普通代码块");
    }
    public Som(){
        System.out.println("子类无参构造器");
    }
    public Som(int A){
        this();
        System.out.println("子类有参构造器");
        this.A = A;
    }
}

看完代码我相信很多朋友如果看了我上一篇文章和我刚刚写的知识点就很很快的说出代码的执行顺序和控制台输出语句的顺序。
如果有朋友还不理解看完下面这创建子类对象时,父子类调用顺序这六点肯定就没问题了,调用顺序如下:

  1. 父类的静态代码块执行和静态属性初始化
  2. 子类的静态代码块执行和静态属性初始化
  3. 父类的普通代码块和普通属性初始化
  4. 父类构造方法
  5. 子类的普通代码块和普通属性初始化
  6. 子类构造方法

还有不懂得朋友,或者就几个步骤不理解的朋友来和我一起来一步一步debug吧。
首先我们将断点打在第一次new对象这里

image.png
接下来步入对象,我们就可以看到首先是进入了父类开始执行上面的步骤1. 父类的静态代码块执行和静态属性初始化.

image.png
控制台输出:

image.png
我们继续下一步debug就可以看到开始进入步骤2. 子类的静态代码块执行和静态属性初始化

image.png
控制台输出:

image.png
然后我们继续下一步注意这里两个类的初始化已经完成了,点击下一步会看到程序会重新回到开始new对象这里,然后我们再次步入。

image.png
接下来按照上面的步骤我们应该是进入父类开始对父类的普通代码块和普通属性初始化,但是点击下一步会发现一个奇怪的现象:程序先进入到了子类的有参构造器调用this();进入子类的无参构造器。

image.png
这是为什么呢?其实原因很简单我们以这段代码为例

public class 练习8 {
    public static void main(String[] args) {
        Som som = new Som();
    }
}

class Father{
    public Father(){
        System.out.println("父类无参构造器");
    }
}

class Som extends Father{
    public Som(){
        System.out.println("子类无参构造器");
    }

}

首先老套路我们先下断点然后步入

image.png
接下来我们先到的是子类的无参构造器,然后当你再次步入时就直接到了父类的无参构造器,不知道原因的朋友肯定会感到很迷,其实在到子类的无参构造器时程序是进入到了构造器内部,Java在构造器的内部默认调用了super();进入父类无参构造器

image.png
这样说可能还是有点迷糊,我们改一下代码就一目了然了,我们在构造器中手动加上super(); 就可以很明显的看到程序进入了子类的无参构造器执行了super();

image.png
当然在平时我们一般是不用去手动添加super();的java会自动默认的去调用,但是注意我说的是一般情况,是有特殊情况的,当你重写了父类的无参构造器将无参构造器改为了有参构造器程序就会报错。

image.png
为什么呢,因为Java默认而且只会调用的是父类的无参构造

image.png
我们有两种解决办法第一种就是在父类中加上无参构造器,第二种是在子类构造器中写上super(int);显示的让Java调用父类的有参构造器。

image.png

image.png
OK这个讲完我们重新回到前面就可以理解了:程序先进入到了子类的有参构造器调用this();进入子类的无参构造器。然后在子类的无参构造器中调用super(); 到父类的无参构造器。

image.png

image.png
接下来的程序执行步骤就很明了了先是执行3. 父类的普通代码块和普通属性初始化然后执行4. 父类构造方法控制台输出如下

image.png
然后继续执行步骤5. 子类的普通代码块和普通属性初始化和步骤6. 子类构造方法控制台输出如下

image.png
好的到这里就全部都讲完了,相信能看到这里的朋友我的文章肯定能对你多多少少有一定的帮助吧,那就帮忙点赞分享一下吧,栓Q!