java的初始化顺序
java编译器保证所有变量在使用之前都得到初始化。
虽然话很简单,但是理解起来还是很难的,尤其是加入static、继承和多态等等java特性,到现在我还没完全理解透,写此博文纯粹总结一下。
1、首先是类装载阶段,在java中有两个隐藏了的方法init和clinit,在类的装载阶段,jvm会调用clinit方法对静态类变量和static初始化块进行初始化,这个阶段并没有实例化任何变量,所有的static对象和static代码段都会在加载时依程序中的顺序(即,定义类时的书写顺序)而依次初始化,当然只会被初始化一次。这个阶段加载顺序是先基类(到达Object类为止)后子类。
2、进入实例化对象阶段,Java编译器会为它的每一个类都至少生成一个实例初始化方法,在Class文件中,被称为"<init>",在这个阶段开始,对象中的所有基本类型都会被设为默认值,对象引用被设为null,之后才会进入类的构造函数,这时会首先调用基类的构造函数,在基类构造器完成之后,对象的成员变量按其次序被初始化(还包括非静态初始化块),在这一切完成之后,该类构造函数的其余部分才会被执行。
如果在基类构造函数中,存在一个动态绑定的方法调用,它是可以调用导出类的方法的,但是由于这时导出类的成员变量还没有初始化,所以如果在多态方法中存在使用成员变量的情况,那么这个成员变量的输出可能并不是我们想要的,而是二进制零。
如果将某个类的实例设为该类的一个非静态成员变量,那么就会进入无限递归中,最后导致堆栈溢出,设为静态变量就不会有问题,因为静态变量之后被初始化一次。但是有个疑问,就是设为静态变量之后,在第(1)阶段对这个变量进行实例化时,如果这个类中有静态代码块且位于这个静态变量之后,那么在这个实例化过程中,这个静态代码块并不会被调用,而是当这个静态变量实例化完成之后才会被调用,这个地方不理解为什么。
上代码,这段代码来自:http://www.cnblogs.com/youngto/archive/2013/01/26/2877021.html,如有不妥,请联系我,我会删除的。
1 package com.lyj.init; 2 3 /** 4 * 父类Foo,实现控制台输出 5 * 6 * @author youngto 7 * @since 2013-01-25 8 */ 9 class Foo { 10 11 private int index = 100; 12 private String str = "hello"; 13 static private int i = 100; 14 15 //静态代码块 16 static { 17 System.out.println("Foo static"); 18 } 19 20 //初始化代码块 21 { 22 System.out.println("Foo initialization"); 23 } 24 25 public Foo() { 26 System.out.println("Foo constructor"); 27 System.out.println(printIndex()); 28 } 29 30 protected int printIndex() { 31 return index; 32 } 33 34 } 35 36 /** 37 * 子类Bar,实现控制台输出 38 * 39 * @author youngto 40 * @since 2013-01-25 41 */ 42 public class Bar extends Foo{ 43 44 private int index = 100; 45 static Bar bar = new Bar(); 46 static String str = "hello"; 47 //静态代码块 48 static{ 49 System.out.println("Bar static"); 50 } 51 52 //初始化代码块 53 { 54 System.out.println("Bar initialization"); 55 } 56 57 public Bar() { 58 System.out.println("Bar constructor"); 59 System.out.println(printIndex()); 60 } 61 62 @Override 63 protected int printIndex() { 64 // System.out.println(bar); 65 return index; 66 } 67 68 public static void main(String[] args) { 69 System.out.println("hello"); 70 Foo foo = new Bar(); 71 System.out.println(foo.printIndex()); 72 foo = new Bar(); 73 } 74 75 }
最后的输出结果:
1 Foo static 2 Foo initialization 3 Foo constructor 4 0 5 Bar initialization 6 Bar constructor 7 100 8 Bar static 9 hello 10 Foo initialization 11 Foo constructor 12 0 13 Bar initialization 14 Bar constructor 15 100 16 100 17 Foo initialization 18 Foo constructor 19 0 20 Bar initialization 21 Bar constructor 22 100
整个程序的执行顺序是,首先jvm试图调用Bar的静态main方法,所以类加载器要事先进行相关的加载、连接、初始化工作,由于Bar继承自Foo,所以clinit先执行Foo的静态代码块并初始化静态变量,然后执行子类的静态代码块,当初始化子类对象之前,java保证父类得到初始化。实例化静态引用bar的工作由init完成,这个过程并不会执行clinit的任务,比如执行静态代码块,所以第8行“Bar static”不是在第4行之后,而是等静态变量bar的成员变量初始化之后才会打印出来。
java的初始化总是推迟到对静态方法(构造函数是隐式静态的)或者非常数静态成员进行首次引用时执行,使用.class语法获得类的引用并不会引发初始化,如果直接调用类的常数静态成员(即static final, 编译期常量),那么这个值并不需要对类进行初始化就可以被读取。