创建子类实例时,会先调用父类构造函数,这里改变了父类的修饰符,为什么输出结果不一样

public class Solution {
    public static void main(String[] args) {
        new B(6);
    }

    public static class A {
        private int f1 = 7;

        public A(int f1) {
            this.f1 = f1;
            initialize();
        }

        protected void initialize() {// 修饰符protected, 输出为0 9, 为priviate时, 输出6 9
            System.out.println(f1);
        }
    }

    public static class B extends A {
        protected int f1 = 3;

        public B(int f1) {
            super(f1);
            this.f1 += f1;
            initialize();
        }

        protected void initialize() {
            System.out.println(f1);
        }
    }
}

这问题纠结半天还是没懂,我知道当创建子类object时, 会先用到父类的构造函数,然后再是自己的构造函数。

这道题父类的方法为protected void initialize()时, 输出是
0
9

当把父类方法改为priviate void initialize(), 输出是
6
9

请问一下这是为什么?

无论如何,都不应该在构造函数内调用虚成员函数。因为基类构造函数在派生类构造完成之前调用,此时派生类的构造函数没有调用,成员处于不确定的状态。
你的代码的困惑不必深究,但是这说明这种代码在实际开发中应该杜绝。

首先,你需要知道一个前提1,虽然Java中有继承这个东西,但是子类无法继承父类private修饰的私有成员,也就意味着不存在重写。

然后,你还需要知道一个前提2,子类继承并重写了父类方法时该方法引用指向子类的方法,没有重写的方法引用指向父类,也就是说调用继承并重写的方法时调用的是子类重写的方法,没有重写的调用的是父类的方法

最后你需要知道Java中类实例化顺序(大致顺序)

1、在方法区先加载父类,再加载子类;
2、在栈中申请空间,声明变量(假设是P);
3、在堆内存中开辟空间,分配对象地址;
4、在对象空间中,对对象的属性(包括父类的属性)进行默认初始化;
5、子类构造方法进栈;
6、显示初始化父类的属性;
7、父类构造方法进栈,执行完毕出栈;
8、显示初始化子类的属性;
9、初始化完毕后,将堆内存中的地址值赋给引用变量P,子类构造方法执行完毕出栈;

然后开始分析你的代码执行过程

protected的时候:

1.加载了父类和子类
2.默认初始化父类和子类的属性即`A.f1 = 0;`和`B.f1 = 0;`
3.为父类属性进行显式赋值即`A.f1 = 7;`
4.执行父类构造方法,即`A.f1 = 6;`,调用`initialize()`,参考前提2,此时父类构造方法中调用的是子类继承重写的的`initialize`方法,打印`B.f1`即`0`
5.显式初始化子类属性,即`B.f1 = 3;`
6.继续执行子类构造方法,即`B.f1 = B.f1 + f1;`,即`B.f1 = 3 + 6`即为`9`,调用子类继承重写的方法`initialize()`打印`9`

private的时候,与protected类似,只是在第4步不同,第6步稍有不同但不影响结果

1.加载了父类和子类
2.默认初始化父类和子类的属性即`A.f1 = 0;`和`B.f1 = 0;`
3.为父类属性进行显式赋值即`A.f1 = 7;`
4.执行父类构造方法,即`A.f1 = 6;`,调用`initialize()`,参考前提1,子类无法重写父类的`initialize`方法,也就是说子类中的`initialize`是它的自定义方法,此时父类构造方法中就只能调用父类自己的`initialize`方法,也就是打印`A.f1`即6
5.显式初始化子类属性,即`B.f1 = 3;`
6.继续执行子类构造方法,即`B.f1 = B.f1 + f1;`,即`B.f1 = 3 + 6`即为`9`,调用子类自定义方法`initialize()`打印`9`

其实你多打几个断点debug一下,看看代码的执行顺序,以及执行时变量的值,你就能得出上面的结论。

还有因为你的写法是内部类,所以子类中是可以直接访问父类的私有属性的,这个你可以测试一下。

我只是大致的解释,其实并不完善,因为我对Java底层原理并不是很了解,所以如果你想知道根本原因,需要去研究Java的实现原理。

最后,像一楼所说,不要在构造函数中进行这种操作

private 是当前实例独有的,protected是子类共有的,private无法被复写,相当于父类,和子类是两个不同方法,你可以在子类方法上加上@Override注解,就清楚了,所以,一个是0,一个是6