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, 编译期常量),那么这个值并不需要对类进行初始化就可以被读取。

你可能感兴趣的:(java)