首先父类是肯定要优先于子类加载的
1. 类的加载过程中,准备阶段优先于初始化阶段。但是准备阶段只会给static分配空间,不会赋值(默认值)。但是会给final static赋值。
所以在 dfs = 20/0时,仅仅加载了父类的静态部分。在分配完父类的静态字段空间之后,要给子类静态常量分配空间赋值时 报错。是准备阶段就报错
虽然如果是ds = 20/0时,看起来输出一样 ,貌似也是仅仅加载了父类的静态部分,但报错其实已经存在于下一个故事里了。
因为此时是先给父类静态部分空间,再给子类静态部分空间。等到类加载的连接阶段结束之后,走到了初始化阶段。初始化时先给静态字段赋值,然后报错,是初始化阶段报错
2. 在静态部分都没有问题的时候。那么类在加载的时候会顺利度过 准备阶段和初始化阶段的静态字段赋值阶段。那么下一步初始化时什么顺序呢
在d = 20/0 或者 df = 20/0时,都可以观测到,父类的变量赋值以及构造方法都已经结束
所以证明初始化阶段是要先将父类的变量赋值和构造方法一同初始化,再去找子类。而普通常量在其中并没有更高优先级
结论 所以类的加载时顺序是
准备阶段 : 父类的静态字段 (static 分配空间不赋值(默认值) final static 分配空间和赋值)
子类的静态字段(同上)
初始化阶段 父类的静态字段赋值 包括静态代码块
子类的静态字段赋值
父类的常量 变量 赋值以及构造方法执行
子类的常量 变量辅助 以及构造方法执行
所以说,子类的构造方法一定要在父类的构造方法执行之后。
当然子类构造方法可以显示调用父类的构造方法,也可以隐式调用,隐式的前提是父类必须定义无参构造方法。
System.out.println(new D(2).d);
}
}
class C {
public int c = 10;
public static int cs = 10;
public final int cf = 10;
static {
System.out.println("init C");
}
public C(int x) {
System.out.println("CONSTRUCT C");
}
public C() {
System.out.println("CONSTRUCT C no param");
}
}
class D extends C {
public int d = 20; //= 20/0 ;
public static int ds = 20; //= 20/0 ;
public final int df = 20; //= 20/0 ;
public final static int dfs = 20; //= 20/0 ;
static {
System.out.println("init D");
}
public D(int x) {
// 子类加载必然会要求父类被调用,可以显示调用父类的构造方法,也可以隐式调用,隐式的前提是父类必须写好构造方法。
// 在类的加载流程中,初始化这一部分是必须的
// super(x);
System.out.println("CONSTRUCT D no param");
}
}
// 没有被使用的类不会被加载
class E extends C {
static {
System.out.println("init E");
}
public E() {
System.out.println("CONSTRUCT E");
}
}
1. 类的加载过程中,准备阶段优先于初始化阶段。但是准备阶段只会给static分配空间,不会赋值(默认值)。但是会给final static赋值。
所以在 dfs = 20/0时,仅仅加载了父类的静态部分。在分配完父类的静态字段空间之后,要给子类静态常量分配空间赋值时 报错。是准备阶段就报错
虽然如果是ds = 20/0时,看起来输出一样 ,貌似也是仅仅加载了父类的静态部分,但报错其实已经存在于下一个故事里了。
因为此时是先给父类静态部分空间,再给子类静态部分空间。等到类加载的连接阶段结束之后,走到了初始化阶段。初始化时先给静态字段赋值,然后报错,是初始化阶段报错
2. 在静态部分都没有问题的时候。那么类在加载的时候会顺利度过 准备阶段和初始化阶段的静态字段赋值阶段。那么下一步初始化时什么顺序呢
在d = 20/0 或者 df = 20/0时,都可以观测到,父类的变量赋值以及构造方法都已经结束
所以证明初始化阶段是要先将父类的变量赋值和构造方法一同初始化,再去找子类。而普通常量在其中并没有更高优先级
结论 所以类的加载时顺序是
准备阶段 : 父类的静态字段 (static 分配空间不赋值(默认值) final static 分配空间和赋值)
子类的静态字段(同上)
初始化阶段 父类的静态字段赋值 包括静态代码块
子类的静态字段赋值
父类的常量 变量 赋值以及构造方法执行
子类的常量 变量辅助 以及构造方法执行
所以说,子类的构造方法一定要在父类的构造方法执行之后。
当然子类构造方法可以显示调用父类的构造方法,也可以隐式调用,隐式的前提是父类必须定义无参构造方法。
public class TestLoadClass {
public static void main(String[] args) {System.out.println(new D(2).d);
}
}
class C {
public int c = 10;
public static int cs = 10;
public final int cf = 10;
static {
System.out.println("init C");
}
public C(int x) {
System.out.println("CONSTRUCT C");
}
public C() {
System.out.println("CONSTRUCT C no param");
}
}
class D extends C {
public int d = 20; //= 20/0 ;
public static int ds = 20; //= 20/0 ;
public final int df = 20; //= 20/0 ;
public final static int dfs = 20; //= 20/0 ;
static {
System.out.println("init D");
}
public D(int x) {
// 子类加载必然会要求父类被调用,可以显示调用父类的构造方法,也可以隐式调用,隐式的前提是父类必须写好构造方法。
// 在类的加载流程中,初始化这一部分是必须的
// super(x);
System.out.println("CONSTRUCT D no param");
}
}
// 没有被使用的类不会被加载
class E extends C {
static {
System.out.println("init E");
}
public E() {
System.out.println("CONSTRUCT E");
}
}