子类父类成员的初始化顺序以及遇见的错误

引用:

https://blog.csdn.net/moakun/article/details/80564311

文中提到

子类的初始化过程。

    父类static修饰的模块

        |

    子类static修饰模块

        |

    父类实例变量和非static块

        |

    父类对应构造函数。当子类对应构造函数中没有显示调用时调用的是父类默认的构造函数。

        |

    子类实例变量和非static块

        |

    子类构造函数

上述中如子类实例变量和非static块,这两者的执行顺序取决于在类文件中的书写顺序,静态的与之相同,也是取决于类文件中的书写顺序

现在我们思考,将

1.类的成员变量在声明处进行初始化

或者在

2.使用代码块初始化

或者在

3..构造函数中初始化

的应用场景及区别

上述三种的对应代码分别是:)

1.

public class Son  extends Father{
    private Foo nonStaticMem=new Foo();
    //private static Foo staticMem;
    /*
    {
        nonStaticMem=new Foo();
    }
    */
    public Son(){
        //nonStaticMem=new Foo();
        System.out.println("directly init non static member varibles");
    }

    @Override
    public void override() {

    }
}
 

2.

public class Son  extends Father{
    private Foo nonStaticMem;
    //private static Foo staticMem;
    
    {
        nonStaticMem=new Foo();
    }
    
    public Son(){
        //nonStaticMem=new Foo();
        System.out.println("use non static code block init non static member varibles");
    }

    @Override
    public void override() {

    }
}

3.

public class Son  extends Father{
    private Foo nonStaticMem;
    //private static Foo staticMem;
    /*
    {
        nonStaticMem=new Foo();
    }
    */
    public Son(){
        nonStaticMem=new Foo();
        System.out.println("use constructor init non static member varibles");
    }

    @Override
    public void override() {

    }
}

上面的分析可以对应至静态成员变量,只是分析时需要按静态成员的初始化顺序分析

1.既然在声明处直接初始化和代码块初始化方式仅仅取决于代码的书写顺序,那么两者的作用在初始化成员变量上是相同的,但是代码块除了能够初始化成员变量之外,还能够做一些其他工作,因此写代码时倾向于在声明处直接初始化成员变量(如果可以的话),而在代码块中做一些环境检查,变量初始化检查,以及其他一些除初始化之外的额外的动作,这些动作经常不涉及成员方法的调用

2.在声明处直接初始化这种方式无非是想给成员变量一个默认值,这种方式往往对映着无参构造器或者其他的缺少某个不带重要的参数的构造器的使用,这两种方式初始化成员变量效果上是相同的,如果你在构造函数中只是简单的赋值,请考虑将这部分逻辑移至声明处直接初始化,而在构造函数中去使用某个成员方法(如果需要的话)

我发现的错误:

class Parent{
           int x=10;
    public Parent(){
        add(2);
    }
    void add(int y){
        x+=y;
    }
}
     class Child extends Parent{
    int x=9;
    void add(int y){
        x+=y;
    }
    public static void main(String[] args){
        Parent p=new Child();
        Child child=(Child)p;
        System.out.println(p.x);
        System.out.println(child.x);
    }
}

上述代码的输出结果是:

依据上面的分析,其调用过程应该是:

1.在main函数中检测到new调用,尝试去加载类,发现此时尚未加载Child,加载Child类

2.发现Child类继承自Parent,转去加载Parent

3.Parent没有静态成员或者静态代码块,加载结束

4.调用new字节码指令,此时父类加载完毕,首先初始化父类成员变量,此时parent.x=10;

5.发现父类存在成员变量,初始化之,无非静态代码块,进行第6步

6.调用父类构造函数,在其中发现了add(2)方法,向虚拟机栈推送栈帧,发现当前类类型信息是Child。

7.调用子类的add(2)方法,注意此时子类成员变量并未初始化,构造函数也未调用,x只是在编译时赋了初值0;

8.调用子类add(2)之后,此时child.x=2;

9.父类构造函数调用完毕,开始准备调用子类构造函数

10.初始化子类的成员变量,即执行子类的int x=9,此时child.x=9;

11.调用子类的构造函数,子类构造函数无任何操作,整个调用过程结束。

因此最终输出

10,9

基于此,你也应该能够解释为什么有时候在子类成员变量声明处直接初始化但是在运行时仍会出现空指针异常的现象

tips:

猜你喜欢

转载自blog.csdn.net/WK_SDU/article/details/83620391
今日推荐