在构造方法内不应调用可被覆写的方法

在学习编码规范时,明确要求在构造方法内不应调用可被覆写的方法。学习了几篇文章后,发现这里面涉及类的初始化流程,还是挺复杂的,怕自己只是貌似明白了,所以还是尝试用自己的话写一写,指不定哪天就被问到呢,先记下来。

类的初始化顺序

一般类加载过程

通过跑完下面代码,我们可以看到一个类的加载过程为:

静态变量/静态代码块 -> main方法 -> 非静态变量/代码块 -> 构造方法

public class SimpleClassLoadTest {
    public Test testDynamic = new Test("Dynamic");
    public static Test testStatic = new Test("Static");

    public static void main(String[] args) {
        System.out.println("Run into mian");
        new SimpleClassLoadTest();
    }

    {
        System.out.println("Code block");
    }

    static {
        System.out.println("Static code block");
    }

    public static Test testStaticSecond = new Test("Static Second");


    public SimpleClassLoadTest() {
        System.out.println("Constructor SimpleClassLoadTest");
    }
}

class Test {
    public Test(String flag) {
        System.out.println("Constructor Test flag: " + flag);
    }
}

// 输出:
Constructor Test flag: Static
Static code block
Constructor Test flag: Static Second
Run into mian
Constructor Test flag: Dynamic
Code block
Constructor SimpleClassLoadTest

如输出所示,其中静态变量和静态代码块先后顺序决定了他们的执行顺序,所以限制性flag为Static的Test初始化,再执行静态块代码,最后执行flag为Second的Test初始化。

有继承关系的类加载过程

通过跑完下面代码,我们可以看到有继承关系的类的加载过程为:

父类静态变量/静态代码块
子类静态变量/静态代码块
父类变量/代码块
父类构造器
子类变量/代码块
子类构造器

public class ExtendClassLoadTest {
    public ExtendClassLoadTest() {
        System.out.println("Constructor - ExtendClassLoadTest");
        new Son();
    }

    public static void main(String[] args) {
        new ExtendClassLoadTest();
    }
}

class Father {
    public String dynmaicStr = "Father - dynmaicStr";
    public static String staticStr = "Father - staticStr";
    public int value;

    {
        System.out.println("value = " + value + " " + dynmaicStr);
    }

    static {
        System.out.println(staticStr);
    }

    public Father() {
        System.out.println("constructor Father value = " + value);
        add();
        System.out.println("constructor Father after add value = " + value);
    }

    protected void add() {
        value += 12;
    }
}

class Son extends Father {
    public String dynmaicStr = "Son - dynmaicStr";
    public static String staticStr = "Son - staticStr";

    {
        System.out.println("value = " + value + " " + dynmaicStr);
    }

    static {
        System.out.println(staticStr);
    }

    @Override
    protected void add() {
        value += 44;
    }

    public Son() {
        System.out.println("constructor Son value = " + value);
        add();
        System.out.println("constructor Son after add value = " + value);
    }
}

// 输出:
Constructor - ExtendClassLoadTest
Father - staticStr
Son - staticStr
value = 0 Father - dynmaicStr
constructor Father value = 0
constructor Father after add value = 44
value = 44 Son - dynmaicStr
constructor Son value = 44
constructor Son after add value = 88

上述输出不难看出,父子之间先父类再子类,动静之间静态后动态,变量/代码块在构造器前。

细心的同学估计已经发现了子类构造方法中打印的value值有些不合理,这也是本篇文章所要讨论的关键所在。

测试代码

当父类方法被子类覆写后,用父类在构造方法中实际调用的是子类的覆写方法。当父类构造器中调用add()时他实际调用的是子类的add(),所以会打出constructor Father after add value = 44。我们知道对于成员变量,类在初始化时都会有初始化值,8个基本类型分别是0,0.0,false,其他引用类型是null,如果add()调用的成员变量是Integer的,则会有空指针异常抛出导致程序崩溃,而这个问题在编译时不会暴露出来,这也就是为什么题目中写道“在构造方法内不应调用可被覆写的方法”。

根因就是父类的构造器在子类构造器之前执行,但父类调用的成员方法优先使用子类的覆写方法。

public class NoUseOverrideMethodInFatherConstructorTest {
    public static void main(String[] args) {
        Base base = new Base();
        base.printValue();

        Base sub = new Sub();
        sub.printValue();
    }
}

class Base {
    protected Integer value;

    protected void show() {
        System.out.println("Base show");
    }

    protected void add() {
    }

    public Base() {
        show();
        add();
    }

    public void printValue() {
        System.out.println("Base value = " + value);
    }
}

class Sub extends Base {
    public Sub() {
        value = new Integer(12);
    }

    @Override
    protected void add() {
        value++;
    }

    @Override
    public void printValue() {
        System.out.println("Sub value = " + value);
    }
}

测试代码路径:
thinkinginjava/chapter07reusingclasses
参考:
JAVA类初始化顺序总结

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在父类的构造方法调用可能被子类覆方法可能会引发一些意想不到的问题,因为在子类的构造方法中,它的成员变量可能还未被初始化完成,此时调用被覆方法可能会导致意想不到的结果,例如空指针异常等。 具体来说,当父类的构造方法调用一个被子类覆方法时,如果子类中的该方法引用了子类中的成员变量,而这些成员变量在父类的构造方法中还未被初始化,那么就可能会导致子类中的该方法出现异常。 例如,以下代码演示了在父类的构造方法调用被子类覆方法可能导致的问题: ```java public class Animal { public Animal() { move(); // 调用被子类覆方法 } public void move() { System.out.println("Animal is moving"); } } public class Dog extends Animal { private String name; public Dog(String name) { super(); this.name = name; } @Override public void move() { System.out.println(name + " is running"); } } public class Test { public static void main(String[] args) { Dog dog = new Dog("Tom"); } } ``` 在上面的代码中,父类Animal的构造方法调用了被子类Dog覆方法move(),而子类Dog中的move()方法引用了成员变量name,而这个成员变量在父类Animal的构造方法中还未被初始化。因此,在运行上面的代码时,会出现空指针异常。 为了避免这种问题,我们应该尽量避免在父类的构造方法调用被子类覆方法。如果必须要调用,可以将该方法设置为final类型,或者将该方法放在父类的构造方法之后进行调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kiba_zwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值