实例探究java成员变量的初始化顺序

1,248 阅读3分钟

先来看一个例子

// initialization/InitTest.java
package initialization;

class InitTest {

	private String mString = "mString1";
	private static String sString = "sString1";

	static {
		sString = "sString2";
	}

	{
		mString = "mString2";
	}

	InitTest() {
		mString = "mString3";
	}

}

InitTest类中有两个变量,一个为静态变量,一个为成员变量。其中包含了几种变量初始化的方式:

  1. 定义时初始化
  2. 静态方法块对静态变量初始化
  3. 实例方法块对成员变量初始化
  4. 构造方法对成员变量初始化

当然对成员变量初始化的地方,也可以对静态变量初始化,反之则不一定可以。

那究竟上面的初始化代码是如何执行的呢?

我们首先进到initialization所在的目录,然后用javac对InitTest.java进行编译

javac initialization/InitTest.java 

此时会在initialization目录下生成InitTest.class

然后我们使用javap -c命令反编译InitTest.class

javap -c initialization/InitTest.class 

输出如下:

Compiled from "InitTest.java"
class initialization.InitTest {
  initialization.InitTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String mString1
       7: putfield      #3                  // Field mString:Ljava/lang/String;
      10: aload_0
      11: ldc           #4                  // String mString2
      13: putfield      #3                  // Field mString:Ljava/lang/String;
      16: ldc           #5                  // String sString3
      18: putstatic     #6                  // Field sString:Ljava/lang/String;
      21: aload_0
      22: ldc           #7                  // String mString3
      24: putfield      #3                  // Field mString:Ljava/lang/String;
      27: return

  static {};
    Code:
       0: ldc           #8                  // String sString1
       2: putstatic     #6                  // Field sString:Ljava/lang/String;
       5: ldc           #9                  // String sString2
       7: putstatic     #6                  // Field sString:Ljava/lang/String;
      10: return
}

可以看到InitTest里有两个方法,一个是构造方法,一个是静态方法。定义初始化和方法块初始化的代码都被合并到了构造方法和静态方法块之中。

构造方法中变量初始化的顺序为:

  1. 父类构造函数
  2. 变量定义初始化
  3. 实例初始化方法块
  4. 构造函数初始化

静态方法块中的顺序为:

  1. 变量定义初始化
  2. 静态初始化方法块

现在问题来了,变量的定义和初始化方法是否可以交换顺序?交换顺序后能否编译通过?如果可以,对初始化顺序是否有影响?

比如我们将代码改成下面这样:

package initialization;

class InitTest {
	static {
		sString = "sString2";
	}

	{
		mString = "mString2";
	}

	private String mString = "mString1";
	private static String sString = "sString1";

	InitTest() {
		sString = "sString3";
		mString = "mString3";
	}
}

重新javac编译,发现可以通过,反编译后代码如下:

Compiled from "InitTest.java"
class initialization.InitTest {
  initialization.InitTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String mString2
       7: putfield      #3                  // Field mString:Ljava/lang/String;
      10: aload_0
      11: ldc           #4                  // String mString1
      13: putfield      #3                  // Field mString:Ljava/lang/String;
      16: ldc           #5                  // String sString3
      18: putstatic     #6                  // Field sString:Ljava/lang/String;
      21: aload_0
      22: ldc           #7                  // String mString3
      24: putfield      #3                  // Field mString:Ljava/lang/String;
      27: return

  static {};
    Code:
       0: ldc           #8                  // String sString2
       2: putstatic     #6                  // Field sString:Ljava/lang/String;
       5: ldc           #9                  // String sString1
       7: putstatic     #6                  // Field sString:Ljava/lang/String;
      10: return
}

我们发现初始化的顺序也变了,其中方法块中的初始化跑到了定义初始化的前面,可以这两者的顺序和其在代码中的位置保持一致。

另外我们知道静态初始化在构造方法之前调用。 从而我们可以知道,完整的初始化顺序为:

  1. 父类静态方法
  2. 子类静态方法
  3. 父类构造方法
  4. 子类构造方法

其中静态方法中包含:定义初始化和静态方法块,其顺序和代码中的位置一致。 其中构造方法中包含:(变量定义初始化和实例方法块)、构造方法初始化。定义初始化和实例方法块的顺序与代码中的位置一致。