Java类和对象在初始化时主要按照以下顺序进行:
父类静态变量和静态块(这两者的初始化顺序按照定义顺序来) --> 子类静态变量和静态块 --> 父类普通成员和块 --> 父类构造函数 --> 子类普通成员和块 --> 子类构造函数
个人理解:父类静态变量静态块 和 子类静态变量静态块 的初始化应该叫做类的初始化,而其他四步应该叫做类相关对象的初始化。因为后四步与对象密切相关,没有对象的访问后四步就不会出现,这个在后面的例子中可以看出来。有时候可能只会激发类的初始化,而没有对象初始化步骤。
基本代码:
本文中的例子都基于以下代码,各个例子稍有改动。
public class Sample {
Sample(String s) {
System.out.println(s);
}
Sample() {
System.out.println("Sample默认构造函数被调用");
}
}
public class Parent {
static int x = 1;
static Sample sam_pa = new Sample("父类静态成员sam_pa初始化");
Sample sam1_pa=new Sample("父类sam1_pa成员初始化");
static {
System.out.println("父类static块2 执行");
}
Parent() {
System.out.println("父类默认构造函数调用");
}
Parent(String s) {
System.out.println("父类字符串构造函数调用");
}
static {
System.out.println("父类static块1 执行");
}
{
System.out.println("父类块1执行");
}
Sample sam2_pa=new Sample("父类sam2_pa成员初始化");
{
System.out.println("父类块2执行");
}
}
public class Child extends Parent{
static final int y = 2;
static {
System.out.println("子类static块1 执行");
}
Sample sam1_ch=new Sample("子类sam1_ch成员初始化");
static Sample sam_ch = new Sample("子类静态成员sam_ch初始化");
Child() {
System.out.println("子类默认构造函数调用");
}
static {
System.out.println("子类static块2 执行");
}
Sample sam2_ch=new Sample("子类sam2_ch成员初始化");
}
public class StartClass {
public static void main(String[] args){
new Child();
}
}
尝试1:
以StartClass的main函数为入口启动程序。并查看打印结果:
父类静态成员sam_pa初始化
父类static块2 执行
父类static块1 执行
子类static块1 执行
子类静态成员sam_ch初始化
子类static块2 执行
父类sam1_pa成员初始化
父类块1执行
父类sam2_pa成员初始化
父类块2执行
父类默认构造函数调用
子类sam1_ch成员初始化
子类sam2_ch成员初始化
子类默认构造函数调用
结果完全符合上面所讲的初始化顺序。
尝试2:
将Child类改为:
public class Child extends Parent{
static final int y = 2;
static {
System.out.println("子类static块1 执行");
}
Sample sam1_ch=new Sample("子类sam1_ch成员初始化");
static Sample sam_ch = new Sample("子类静态成员sam_ch初始化");
Child() {
//super("string");
System.out.println("子类默认构造函数调用");
}
static {
System.out.println("子类static块2 执行");
}
Sample sam2_ch=new Sample("子类sam2_ch成员初始化");
public static void main(String[] args){
System.out.println("子类main方法---程序入口");
new Child();
}
}
增加了main函数。并从Child的main函数启动程序。并查看打印结果:
父类静态成员sam_pa初始化
父类static块2 执行
父类static块1 执行
子类static块1 执行
子类静态成员sam_ch初始化
子类static块2 执行
子类main方法---程序入口
父类sam1_pa成员初始化
父类块1执行
父类sam2_pa成员初始化
父类块2执行
父类默认构造函数调用
子类sam1_ch成员初始化
子类sam2_ch成员初始化
子类默认构造函数调用
奇怪的事情发生了,main函数中的第一句怎么出现在了中间,相当尴尬的位置。。。其实这是很正确的。因为当调用main函数时,JVM虚拟机发现这个main函数同时也是Child类的static成员,因而激发了Child类的初始化过程,要初始化Child类,那么先初始化Parent类,因此出现了打印结果的前6行,然后进入main函数,打印第7行。new Child()激发对象的初始化过程,就有了后面的所有行。
尝试3:
将StarClass的main函数改为:
public class StartClass {
public static void main(String[] args){
System.out.println(Child.x);
System.out.println(Child.y);
}
}
打印结果为:
父类静态成员sam_pa初始化
父类static块2 执行
父类static块1 执行
1
2
对Child.x的访问,实际上激发了父类的类初始化过程,因为x为父类的static变量。对Child.y的访问,不会激发任何初始化过程,因为Child.y是常量。
尝试4:
将Child的默认构造函数改为:
Child() {
super("string");
System.out.println("子类默认构造函数调用");
}
打印结果为:
父类静态成员sam_pa初始化
父类static块2 执行
父类static块1 执行
子类static块1 执行
子类静态成员sam_ch初始化
子类static块2 执行
子类main方法---程序入口
父类sam1_pa成员初始化
父类块1执行
父类sam2_pa成员初始化
父类块2执行
父类字符串构造函数调用
子类sam1_ch成员初始化
子类sam2_ch成员初始化
子类默认构造函数调用
如果在构造函数中调用父类指定构造函数,那么父类默认构造函数将不会执行。