类中静态字段的初始化顺序

5
我在理解一个类有自身静态实例时的初始化顺序方面遇到了困难。同时,为什么这种行为似乎对于字符串有所不同。
请看下面的例子:
public class StaticCheck {
    private static StaticCheck INSTANCE = new StaticCheck();    

    private static final List<String> list =
        new ArrayList<String>(Arrays.asList("hello"));
    private static final Map<String, String> map =
        new HashMap<String, String>();  
    private static final  String name = "hello";

    public static StaticCheck getInstance() {
        return INSTANCE;
    }

    private StaticCheck() {
        load();     
    }

    private void load() {
        if(list != null) {
            System.out.println("list is nonnull");
        } else {
            System.out.println("List is null");
        }
        if(name != null) {
            System.out.println("name is nonnull");
        } else {
            System.out.println("name is null");
        }
        if(map != null) {
            System.out.println("Map is nonnull");
        } else {
            System.out.println("Map is null");
        }
    }

    public static void main(String[] args) {
        StaticCheck check = StaticCheck.getInstance();
    }
}

输出:

List is null
name is nonnull
Map is null

我不太清楚为什么字段name不是null。根据类初始化的说明,静态字段在以下情况下被初始化: http://javarevisited.blogspot.in/2012/07/when-class-loading-initialization-java-example.html 看上面的例子,我的想法如下:
  1. 静态字段在Java中在实例初始化之前被初始化。在这里,当我调用静态方法getInstance()时,它将导致类初始化,这意味着静态字段的初始化。在这种情况下,字段maplist不应该为null。
  2. 在上面的例子中,由于字段INSTANCE是静态的,所以它的对象初始化发生在其他字段未初始化时,并且它的构造函数在调用load()时被调用。因此,字段listmap为null。那么为什么name会被初始化呢?我有点困惑。

给你一个提示,你链接的那篇文章说:“类从上到下初始化,因此在顶部声明的字段先于在底部声明的字段进行初始化”。这对你的示例的行为非常重要。 - Radiodef
3个回答

7

String类型的name变量是一个编译时常量,它会在编译时由编译器内联处理。因此,下面这个条件:

if (name != null)

编译后会变成:
if ("hello" != null)

当然,这是正确的。

至于为什么maplist都是null,那是因为在类初始化时,INSTANCE字段被初始化,调用构造函数,进而调用load()方法。需要注意的是,在这个时候,其他static初始化器尚未运行。所以,maplist仍然是null。因此,在load()方法中打印它们将会是null


你能否扩展一下你对第一个问题的回答?可能乍一看不太明显。 - Turing85
@Turing85 已添加了解释。 - Rohit Jain

5
常量 static 变量在初始化任何非 static 变量之前被初始化。 JLS, 第12.4.2节,定义了类的初始化过程:
  1. 否则,记录当前线程正在进行 C 的 Class 对象的初始化,并释放 LC。然后,初始化 C 的静态字段,这些字段是常量变量(§4.12.4,§8.3.2,§9.3.1)。

(其他步骤在此省略)

  1. 接下来,按文本顺序执行类变量初始值设定项和静态初始值设定项,或接口的字段初始值设定项,就像它们是一个单独的块一样。
因此,INSTANCE 首先以文本方式列出,然后才是 listmapname。为什么所有3个变量不仍然是 null 呢?这是因为 name 被一个 常量表达式 初始化;它是一个常量变量。它首先被初始化,因为它是一个常量变量。
请注意,您可以将初始化 INSTANCE 的行移动到 listmap 之后,导致在 INSTANCE 之前初始化 listmap

1

只有原始类型和 String 类型会在编译时被赋值,而且只有当字段是 final 并使用字面量或常量表达式进行初始化时才会被赋值。所有其他的 static 字段和块将在稍后顺序地进行评估。请参考以下示例:

public class StaticExample {

    private static StaticExample instance1 = new StaticExample(1);

    public static void main(String[] args) {
        new StaticExample(3);
    }

    public static boolean b = true;

    public static final boolean fb = true;

    public static Boolean B = true;

    public static final Boolean fB = true;

    public static String S = "text";

    public static final String fS = "text";

    public static final String cS = "te" + "xt"; // constant expression

    public static final String xS = fS.substring(0, 2) + fS.substring(2, 4);

    private static StaticExample instance2 = new StaticExample(2);

    private StaticExample(int no) {
        System.out.println("## " + no + ": ##");
        System.out.println(" b: " + b);
        System.out.println("fb: " + fb);
        System.out.println(" B: " + B);
        System.out.println("fB: " + fB);
        System.out.println(" S: " + S);
        System.out.println("fS: " + fS);
        System.out.println("cS: " + cS);
        System.out.println("xS: " + xS);
        System.out.println();
    }

}

输出:

## 1: ##
 b: false
fb: true
 B: null
fB: null
 S: null
fS: text
cS: text
xS: null

## 2: ##
 b: true
fb: true
 B: true
fB: true
 S: text
fS: text
cS: text
xS: text

## 3: ##
 b: true
fb: true
 B: true
fB: true
 S: text
fS: text
cS: text
xS: text

fb 是一个最终的基本类型,fScS 是常量最终字符串,只有这三个字段是预先分配的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接