Hello Java.
引用1个网上的经典例子并做稍许改动,例子引用自:Java类的加载顺序
public class Animal {
private int i = test();
private static int j = method();
static {
System.out.println("a");
}
Animal(){
System.out.println("b");
}
{
System.out.println("c");
}
public int test(){
System.out.println("d");
return 1;
}
public static int method(){
System.out.println("e");
return 1;
}
}
public class Dog extends Animal{
{
System.out.println("h");
}
private int i = test();
static {
System.out.println("f");
}
private static int j = method();
Dog(){
System.out.println("g");
}
public int test(){
System.out.println("i");
return 1;
}
public static int method(){
System.out.println("j");
return 1;
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println();
Dog dog1 = new Dog();
}
}
执行这段文字,会输出什么内容?
答案是:
eafjicbhig
icbhig
为了方便大家一个个细节去理解, 我换一种方式去提问。
Q:什么时候会进行静态变量的赋值和静态代码块的执行?
A:
- 第一次创建某个类或者某个类的子类的实例
- 访问类的静态变量、调用类的静态方法
- 使用反射方法forName
- 调用主类的main方法(本例子的第一次静态初始化其实属于这个情况,调用了Dog的main方法)
- 类初始化只会进行一次, 上面任何一种情况触发后,之后都不会再引起类初始化操作
Q:初始化某个子类时,也会对父类做静态初始化吗?顺序呢?
A:如果父类之前没有被静态初始化过,那就会进行, 且顺序是先父类再子类。 后面的非静态成员初始化也是如此。所以会先输出eafj。
Q:为什么父类的method不会被子类的method重写?
A:静态方法是类方法,不会被子类重写。毕竟类方法调用时,是必定带上类名的。
Q:为什么第一个输出的是e而不是a?
A:因为类变量的显示赋值代码和静态代码块代码按照从上到下的顺序执行。
Animal的静态初始化过程中,method的调用在static代码块之前,所以先输出e再输出a。
而Dog的静态初始化过程中,method的调用在static代码块之后,因此先输出f,再输出j。
Q:没有在子类的构造器中调用super()时,也会进行父类对象的实例化吗?
A:会的。会自动调用父类的默认构造器。 super()主要是用于需要调用父类的特殊构造器的情况。因此会先进行Animal的对象实例化,再进行Dog的对象实例化。
Q:构造方法、成员显示赋值、非静态代码块(即输出c和h的那2句)的顺序是什么?
A:
- 成员显示赋值、非静态代码块(按定义顺序)
- 构造方法
因此Animal的实例化过程输出icb。(如果对输出i有疑问,见下面一题)
接着进行Dog的实例化,输出hig。
Q:为什么Animal实例化时, i=test()中输出的是i而不是d?
A:因为你真正创建的是Dog子类,Dog子类中的test()方法由于签名和父类test方法一致,因此test方法被重写了。此时即使在父类中调用,也还是用使用子类Dog的方法。除非你new的是Animal。
Q:同上题, 如果test方法都是private或者final属性, 那么上题的情况会有变化吗?
A:因为private和final方法是不能被子类重写的。所以Animal实例化时,i=test输出d。
总结一下顺序:
- 父类静态代变量显式赋值、父类静态代码块(按定义顺序)
- 子类静态变量显式赋值、子类静态代码块(按定义顺序)
- 父类非静态变量显式赋值(父类实例成员变量)、父类非静态代码块(按定义顺序)
- 父类构造函数
- 子类非静态变量(子类实例成员变量)、子类非静态代码块(按定义顺序)
- 子类构造函数