Static变量和实例变量的初始化顺序问题

问题重现

让我们先来看一下以下的程序:

public class StaticInitSequence {
	//-------------------Static fields-------------------
	private static int staticIntVar = 10;
	private static int staticComputeIntVar = (int)(Math.random() * 10);
	private static String staticStrVar = "Static field init(before)";
	private static Object staticRefVar = new Object();
	
	static {
		staticIntVar = 20;
		staticStrVar = "Static block init(before)";
		staticAfterIntVar = 40;
		staticAfterStrVar = "Static block init(after)";
	}
	
	private static int staticAfterIntVar = 30;
	private static String staticAfterStrVar = "Static field init(after)";
	
	//---------------------Instance fields----------------
	private int fieldIntVar = 100;
	private int fieldComputeIntVar = (int)(Math.random() * 100);
	private String fieldStrVar = "Instance field init(before)";
	
	public StaticInitSequence() {
		fieldIntVar = 200;
		fieldStrVar = "Constructor field init(before)";
		
		fieldAfterIntVar = 400;
		fieldAfterStrVar = "Constructor field init(after)";
	}
	
	private int fieldAfterIntVar = 300;
	private String fieldAfterStrVar = "Instance field init(after)";
	
	public void print() {
		System.out.println("----------------Static Fields------------");
		System.out.println("staticIntVar: " + staticIntVar);
		System.out.println("staticComputeIntVar: " + staticComputeIntVar);
		System.out.println("staticStrVar: " + staticStrVar);
		System.out.println("staticRefVar: " + staticRefVar);
		System.out.println("staticAfterIntVar: " + staticAfterIntVar);
		System.out.println("staticAfterStrVar: " + staticAfterStrVar);
		
		System.out.println("-----------------Instance Fields---------");
		System.out.println("fieldIntVar : " + fieldIntVar);
		System.out.println("fieldComputeIntVar : " + fieldComputeIntVar);
		System.out.println("fieldStrVar : " + fieldStrVar);
		System.out.println("fieldAfterIntVar : " + fieldAfterIntVar);
		System.out.println("fieldAfterStrVar : " + fieldAfterStrVar);
	}
}

 

如果我们调用以上类的print()方法(new StaticInitSequence().print()),会有什么样的结果呢?

我自认为,直接对一个字段初始化是编译器提供支持的一种编程方式,这种编程方式可以提高代码的可读性,因为用户可以直接知道一个字段的初始值,而不用到构造函数或者静态语句块里面去找。在Java中,实际编译后的二进制文件中,所有的字段初始化语句都放在了初始化函数中类(静态)初始化函数(<clinit>)或者实例初始化(构造函数/<init>)函数。因此在我的逻辑思维中,在源代码中,初始化函数应该可以改变字段初始化中的值,这样还就可以在字段初始化中提供一个初始值,而在初始化函数中根据需要改变它。然而另我感到意外的是Java中只有实例初始化机制是这样实现的,而静态字段初始化中没有实现这种机制(在C#中不管实例初始化和静态初始化都实现了这种机制),静态字段初始化的顺序是完全根据源代码中定义顺序来初始化的;从耦合的角度,这就是一个顺序耦合的典型。不知道为什么Java要这样实现,是否它有其他方面的问题的考虑?亦或是Java设计者或者Java编译器设计者的一个失误?不管怎么样,用sunjavac编译出来的以上程序的运行结果如下:

----------------Static Fields------------

staticIntVar: 20

staticComputeIntVar: 7

staticStrVar: Static block init(before)

staticRefVar: java.lang.Object@14318bb

staticAfterIntVar: 30

staticAfterStrVar: Static field init(after)

-----------------Instance Fields---------

fieldIntVar : 200

fieldComputeIntVar : 8

fieldStrVar : Constructor field init(before)

fieldAfterIntVar : 400

fieldAfterStrVar : Constructor field init(after)

 

问题解释:

从以上程序生成的二进制代码就可以很好的解释以上的结果:

<clinit>:

         //staticIntVar = 10

     0  bipush 10

     2  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticIntVar : int [22]

       // staticComputeIntVar = (int)(Math.random() * 10)

     5  invokestatic java.lang.Math.random() : double [24]

     8  ldc2_w <Double 10.0> [30]

    11  dmul

12  d2i

13  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticComputeIntVar : int [32]

//staticStrVar = Static field init(before)

    16  ldc <String "Static field init(before)"> [34]

18  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticStrVar : java.lang.String [36]

//staticRefVar = new Object();

    21  new java.lang.Object [3]

    24  dup

    25  invokespecial java.lang.Object() [38]

28  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticRefVar : java.lang.Object [41]

//staticIntVar = 20

    31  bipush 20

33  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticIntVar : int [22]

//staticStrVar = Static block init(before)

    36  ldc <String "Static block init(before)"> [43]

38  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticStrVar : java.lang.String [36]

//staticAfterIntVar = 40

    41  bipush 40

43  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticAfterIntVar : int [45]

//staticAfterStr = Statci block init(after)

    46  ldc <String "Static block init(after)"> [47]

48  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticAfterStrVar : java.lang.String [49]

//staticAfterIntVar = 30

    51  bipush 30

53  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticAfterIntVar : int [45]

//staticAfterStrVar = Static field init(after)

    56  ldc <String "Static field init(after)"> [51]

    58  putstatic org.levin.insidejvm.miscs.staticinit.StaticInitSequence.staticAfterStrVar : java.lang.String [49]

61  return

 

<init>:

       //invoke base constructor

     0  aload_0 [this]

     1  invokespecial java.lang.Object() [38]

     4  aload_0 [this]

       //fieldIntVar = 100

     5  bipush 100

     7  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldIntVar : int [55]

       //fieldComputeIntVar = (int)(Math.random() * 100)

    10  aload_0 [this]

    11  invokestatic java.lang.Math.random() : double [24]

    14  ldc2_w <Double 100.0> [57]

    17  dmul

    18  d2i

19  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldComputeIntVar : int [59]

//fieldStrVar = Instance field init(before)

    22  aload_0 [this]

    23  ldc <String "Instance field init(before)"> [61]

25  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldStrVar : java.lang.String [63]

//fieldAfterIntVar = 300

    28  aload_0 [this]

    29  sipush 300

32  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldAfterIntVar : int [65]

//fieldAfterStrVar = Instance field init(after)

    35  aload_0 [this]

    36  ldc <String "Instance field init(after)"> [67]

38  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldAfterStrVar : java.lang.String [69]

//fieldIntVar = 200

    41  aload_0 [this]

    42  sipush 200

45  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldIntVar : int [55]

//fieldStrVar = Constructor field init(before)

    48  aload_0 [this]

    49  ldc <String "Constructor field init(before)"> [71]

51  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldStrVar : java.lang.String [63]

//fieldAfterIntVar = 400

    54  aload_0 [this]

    55  sipush 400

58  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldAfterIntVar : int [65]

//fieldAfterStrVar = Constructor field init(after)

    61  aload_0 [this]

    62  ldc <String "Constructor field init(after)"> [73]

    64  putfield org.levin.insidejvm.miscs.staticinit.StaticInitSequence.fieldAfterStrVar : java.lang.String [69]

67  return

 

问题延伸

在这里,细心的人可能还会想到另外一个问题,如果StaticInitSequence类还有父类,并且父类中同同时有静态成员初始化,静态语句块初始化,实例成员初始化,构造函数初始化,那会这样的顺序会是怎么样的呢?在Java中,保证父类的初始化要早于子类的初始化,因而如果有父类存在的话,一定是先父类初始化做好以后才做子类的初始化(这一点和C#又有略微的不同,在C#中,子类的字段初始化语句要早于父类的初始化语句和构造函数),并且是先静态初始化再实例初始化。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值