类的初始化顺序:静态变量、静态代码块、非静态变量、非静态代码块、构造方法
- public class InitialOrderTest {
- // 静态变量
- public static String staticField = "静态变量"; 1
- // 变量
- public String field = "变量";
- // 静态初始化块
- static {
- System.out.println(staticField); 1
- System.out.println("静态初始化块"); 2
- }
- // 初始化块
- {
- System.out.println(field); 3
- System.out.println("初始化块"); 4
- }
- // 构造器
public InitialOrderTest() { - System.out.println("构造器"); 5
- }
- public static void main(String[] args) {
- new InitialOrderTest();
- }
- }
- 继承情况下类的初始化顺序:父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造方法、子类非静态变量、子类非静态代码块、子类构造方法
- class Parent {
- // 静态变量
- public static String p_StaticField = "父类--静态变量";
- // 变量
- public String p_Field = "父类--变量";
- // 静态初始化块
- static {
- System.out.println(p_StaticField);
- System.out.println("父类--静态初始化块");
- }
- // 初始化块
- {
- System.out.println(p_Field);
- System.out.println("父类--初始化块");
- }
- // 构造器
- public Parent() {
- System.out.println("父类--构造器");
- }
- }
- public class SubClass extends Parent {
- // 静态变量
- public static String s_StaticField = "子类--静态变量";
- // 变量
- public String s_Field = "子类--变量";
- // 静态初始化块
- static {
- System.out.println(s_StaticField);
- System.out.println("子类--静态初始化块");
- }
- // 初始化块
- {
- System.out.println(s_Field);
- System.out.println("子类--初始化块");
- }
- // 构造器
- public SubClass() {
- System.out.println("子类--构造器");
- }
- // 程序入口
- public static void main(String[] args) {
- new SubClass();
- }
- }
- 那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序。我们以静态变量和静态初始化块为例来进行说明。
- 注意:静态变量和静态代码块的初始化顺序、非静态变量和非静态代码块的初始化顺序和它们编码的先后顺序有关,如下示例:
- public class TestOrder {
- // 静态变量
- public static TestA a = new TestA();
- // 静态初始化块
- static {
- System.out.println("静态初始化块"); 2
- }
- // 静态变量
- public static TestB b = new TestB();
- public static void main(String[] args) {
- new TestOrder();
- }
- }
- class TestA {
- public TestA() {
- System.out.println("Test--A"); 1
- }
- }
- class TestB {
- public TestB() {
- System.out.println("Test--B"); 3
- }
-
}
- 经典面试题:
-
首先,我们从main方法入手,只有一行new对象(第26行)。new对象首先要加载类,这是肯定的了,因此“Test”需要加载类,也就是加载Test的字节码文件到方法区(永久代)中。这时需要把类中的“static”部分处理完成。也就是说,static部分是随着Test的字节码文件进入到永久代的。它的过程是:先把所有的static部分申请空间,然后再给每一个static成员由上至下分配初始值。初始值有两种,一种是Java对于基本数据类型的默认初始值,这个默认初始值在申请空间之时就给每一个成员赋予了,另一种是Java程序员利用“=”对成员进行赋初始值。如图所示,五个静态成员在永久代首先被分配内存。此时,k=0,t1=null,t2=null,i=0,n=0。空间申请完成之后,我们把0赋值给k,虽然k被分配内存之后就是0,但是依然还要把0再赋值给k,因为“=”右边的“0”是程序员对k的赋值。然后是给t1实例一个对象,也就是t1原本是null,现在把new出来的对象赋值给t1,于是就要构建一个Test对象。构建对象时,要把所有的非static部分初始化一份,放入堆内存。这时,你就理解了Java语法中,为什么静态成员是“类名.成员”,而非静态成员是“对象.成员”了。因为所属关系不同。那么我们开始找非静态的成员如图,有j和一个构造块。因此是先给j申请空间,然后运行print("j")方法,把方法的返回值交给j。于是,这个程序的第一段打印结果出来了:打印:此后,k=1 , n=1 , i=1。然后是接着找非静态的部分,就只有构造块了,因此是:此后,k=2 , n=2 , i=2。这时,我们构造对象的准备工作做完了,也就是非静态的代码都执行完了,因此开始实例化对象,Java实例化对象使用构造方法,因此执行:打印:此后,t1不再是null,然后初始化t2,过程和t1一样,因此运行结果是:再然后初始化静态的i,因此是执行:输出打印:然后初始化 n(第6行),直接把n赋值为99。但是什么都不打印。然后再往下是静态块:输出打印:至此,所有的静态部分也都初始化完毕了,可以new Test("init")了:输出打印:所以总体打印如下:总结,其实Java本身也是代码从上往下走的,只不过静态部分和非静态部分在两个次元里。Java的成员有分配空间,赋默认值两个过程,且首先为全体成员申请空间,然后由上至下逐一赋值。输出结果的9、10、11可以理解为所有静态变量、静态代码块都加载完成了,要去创建main方法中的对象,创建main方法的对象依旧走把类中的非静态成员初始化一份放到堆内存中,在执行构造方法,因此9、10、11按顺序依次执行非静态变量、非静态代码块、构造方法