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

posted @ 2013-01-26 22:25  leealways87  阅读(426)  评论(0编辑  收藏  举报