Java初始化顺序
成员变量的初始化顺序
- 实例变量在声明处初始化
- 实例变量在实例初始化块中初始化
- 实例变量在构造器中初始化
- 静态变量在声明处初始化
- 静态变量在静态初始块中初始化
将5种初始化代码无规则的分布在程序中,观察执行的顺序
1 public class InitOrderTest { 2 3 public InitOrderTest(int x, int y) { 4 System.out.println("执行构造器···"); 5 this.x = x; 6 this.y = y; 7 } 8 9 { 10 System.out.println("执行实例初始化块1···"); 11 } 12 private int x; 13 private int y = initY(); 14 15 { 16 System.out.println("执行实例初始化块2···"); 17 x = initX(); 18 } 19 20 static { 21 System.out.println("执行静态初始化块···"); 22 } 23 24 private static int staticX; 25 private static int staticY = initStaticY(); 26 27 static { 28 System.out.println("执行静态初始化块2···"); 29 staticX = initStaticX(); 30 } 31 32 public int initX() { 33 System.out.println("执行实例变量x初始化···"); 34 return 1; 35 } 36 37 public int initY() { 38 System.out.println("执行实例变量y初始化···"); 39 return 1; 40 } 41 42 public static int initStaticX() { 43 System.out.println("执行静态变量staticX初始化···"); 44 return 1; 45 } 46 47 public static int initStaticY() { 48 System.out.println("执行静态变量staticY初始化···"); 49 return 1; 50 } 51 52 public void show() { 53 System.out.printf("x=%d,y=%d,staticX=%d,staticY=%d\n", x, y, staticX, 54 staticY); 55 } 56 57 public static void main(String[] args) { 58 59 System.out.println("执行main方法···"); 60 InitOrderTest iot1 = new InitOrderTest(10, 15); 61 InitOrderTest iot2 = new InitOrderTest(20, 25); 62 iot1.show(); 63 iot2.show(); 64 65 } 66 67 }
运行结果:
分析结果:
1. 当运行InitOrderTest类时,Java虚拟机试图调用该类的main方法时,JVM发现该类尚未加载,因此会首先加载该类,并会执行链接,在链接阶段会为该类的静态方法分配空间,并置为默认值,然后按照初始化在类中的顺序执行静态初始化(只执行一次)。
2. 静态成员的初始化执行完成后,就会执行main方法,创建InitOrderTest类的对象,使用new在堆中为对象分配空间,这时所有的实例变量都设置为默认值。然后执行实例初始化。3种实例初始化方式,首先会按照实例变量在类中声明的初始化顺序执行(这里的顺序指的是初始化在类中声明的顺序,而不是变量在类中声明的顺序),然后执行构造器。
3. 再次创建InitOrderTest类的对象时,不再执行静态初始化。但是每次创建一个对象都要执行一次实例变量初始化。
继承下的初始化
1 public class SubInitOrderTest extends InitOrderTest { 2 3 private int z = initZ(); 4 private static int staticZ = initStaticZ(); 5 6 public SubInitOrderTest(int x, int y, int z) { 7 super(x, y); 8 System.out.println("执行子类构造器···"); 9 this.z = z; 10 } 11 12 public int initZ() { 13 System.out.println("执行子类实例变量z初始化···"); 14 return 1; 15 } 16 17 public static int initStaticZ() { 18 System.out.println("执行子类静态变量staticZ初始化···"); 19 return 1; 20 } 21 22 public static void main(String[] args) { 23 24 new SubInitOrderTest(1, 2, 3); 25 } 26 27 }
运行结果:
分析执行过程:
1. JVM调用子类的main首先加载、链接并尝试初始化子类,而子类继承了父类,所以在初始化子类之前,需要加载、链接并初始化父类,这是一个递归的过程,直到Object类。所以会首先执行父类中静态变量staticX与staticY和静态初始化块。
2. 当父类与子类静态初始化完成后,执行子类的main方法,创建子类的对象。当子类对象创建时,就会执行实例初始化,而子类继承了父类,因此,首先会执行父类的实例初始化,这也是一个递归的过程,直到object类为止。因而父类的实例初始化会在子类的实例初始化之前完成。即首先执行父类中的实例变量x与y,还有两个实例初始化块与构造器,然后执行子类中实例变量z、实例初始化块与构造器。
总结:
- 初始化的顺序可以简单的总结为先静态,后实例,先父类,后子类。对于静态初始化,按照静态变量声明处初始化与静态初始化块在类中出现的顺序执行。对于实例初始化,按照实例变量声明处初始化与实例初始化块在类中出现的顺序执行,然后执行构造器。
- 当心潜在的向前引用,如果使用一个尚未初始化值的变量值,就可能得到错误的结果
- 在构造器中不要调用可由子类重写的方法,调用private与final的方法才是安全的。
- 对于值为编译时常量的final变量,可以认为这样的变量会最先得到初始化。在程序中无法观察到其默认值,即使向前引用这种类型的变量也是如此。