静态变量何时初始化?

105
我想知道静态变量何时初始化为其默认值。 当类被加载时,是否正确创建(分配)静态变量,然后执行静态初始化程序和声明中的初始化? 在什么时候给出默认值?这会导致前向引用问题。
此外,请解释一下有关在Why static fields are not initialized in time?上提出的问题的参考,并特别解释该网站上Kevin Brock所给出的第三点答案。

2
请您编辑您的问题,包括您所引用的语句。 - Oliver Charlesworth
1
你读过Java语言规范吗?它是一份相当易读的文件,特意设计成这样。如果你已经读了,你可能会明白正在发生什么。如果没有,你可以至少问一个更具体的问题... - Maarten Bodewes
我认为这个问答是一个重复的 https://dev59.com/OHA75IYBdhLWcg3wDUgo。 - Stephen C
7个回答

89

来自查看Java静态变量方法

  • 它是属于类而不是对象(实例)的变量
  • 静态变量仅初始化一次,在执行开始时。这些变量将在任何实例变量初始化之前首先初始化
  • 单个副本可以由类的所有实例共享
  • 可以直接通过类名称访问静态变量,而不需要任何对象。

如果您未能刻意初始化实例和类(静态)变量,则会自动将其初始化为标准默认值。虽然局部变量不会自动初始化,但您不能编译未初始化局部变量或在使用前为该局部变量分配值的程序。

编译器实际上所做的是内部产生一个单个类初始化程序,该程序将所有静态变量初始化器和所有静态初始化器代码块按照它们出现在类声明中的顺序组合起来。此单个初始化过程在加载类时自动运行一次。

对于内部类,它们不能具有静态字段

内部类是未显式或隐式声明为static的嵌套类。

...

内部类不能声明静态初始化器(§8.7)或成员接口……

内部类不能声明静态成员,除非它们是常量变量......

请参阅JLS 8.1.3 Inner Classes and Enclosing Instances

在Java中,final字段可以与其声明位置分开初始化,但对于static final字段则不适用。请参见下面的示例。

final class Demo
{
    private final int x;
    private static final int z;  //must be initialized here.

    static 
    {
        z = 10;  //It can be initialized here.
    }

    public Demo(int x)
    {
        this.x=x;  //This is possible.
        //z=15; compiler-error - can not assign a value to a final variable z
    }
}

这是因为与实例变量不同,类型关联的static变量只有一个副本,而不是每个实例都有一个。如果我们试图在构造函数中初始化类型为static finalz,它将尝试重新初始化z这个静态final类型字段,因为构造函数在类的每次实例化时运行,这对于static final字段不应发生。


5
“在静态内部类的情况下,它们不能有静态字段”似乎是一个笔误。内部类是非静态的。 - Daniel Lubarov
你应该使用“然而”代替“虽然”。 - Suraj Jain
当您启动JVM并第一次加载类(当类在任何方式下首次引用时由类加载器完成)时,任何静态块或字段都会被“加载”到JVM中并变得可访问。 - nhoxbypass
1
很遗憾,这个答案包含了一些关于静态变量初始化时间的事实错误。请参考https://dev59.com/OHA75IYBdhLWcg3wDUgo#3499322。 - Stephen C

17

当类被类加载器加载时,静态字段会被初始化。此时会分配默认值。这是按照它们在源代码中出现的顺序完成的。


15

参见:

尤其是最后一点提供了详细的初始化步骤,阐述了静态变量何时初始化以及按照什么顺序初始化(但需要注意的是,首先初始化的是final类变量和编译期常量接口字段)。

我不确定您对第三个问题(假设您指的是嵌套的问题?)的具体问题是什么。详细的顺序说明这将是一个递归初始化请求,因此它将继续初始化。


10

初始化的顺序如下:

  1. 静态初始化块
  2. 实例初始化块
  3. 构造函数

该过程的详细信息在JVM 规范文档中有解释。


6

静态变量

  • 它是属于类而不是对象(实例)的变量。
  • 静态变量仅在执行开始时(当类加载器首次加载类时)初始化一次。
  • 这些变量将在任何实例变量的初始化之前首先初始化。
  • 一个单独的副本可由类的所有实例共享。
  • 静态变量可以直接通过类名访问,不需要任何对象。

5

从另一个问题的代码开始:

class MyClass {
  private static MyClass myClass = new MyClass();
  private static final Object obj = new Object();
  public MyClass() {
    System.out.println(obj); // will print null once
  }
}

对这个类的引用将开始初始化。首先,该类将被标记为已初始化。然后,第一个静态字段将被初始化为MyClass()的新实例。请注意,myClass立即被赋予对空白MyClass实例的引用。该空间存在,但所有值都为null。现在执行构造函数并打印obj,它是null。

现在回到类的初始化:obj被赋予对新实际对象的引用,我们完成了。

如果这是由像MyClass mc = new MyClass();这样的语句触发的,则再次分配新的MyClass实例的空间(并将引用放置在mc中)。构造函数再次执行并再次打印obj,此时obj不再为null。

真正的诀窍在于,当您使用new时,例如WhateverItIs weii = new WhateverItIs(p1, p2);,weii立即被赋予对nulled内存块的引用。JVM将继续初始化值并运行构造函数。但是,如果您以某种方式在其之前引用了weii-例如从另一个线程引用它或通过从类初始化引用-则您将看到一个填充有null值的类实例。


1
一个类在初始化完成之前不会被标记为已初始化——否则没有意义。标记为已初始化几乎是最后一步。参见[JLS 12.4.2](http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.4.2)。 - Dave Newton
@DaveNewton: 一旦某些东西引用该类并开始初始化它,所有后续的引用都将视该类已经初始化。 它们不会尝试初始化它,并且不会等待它被初始化。 因此,从程序启动时看起来不为null的字段实际上可能会在一段时间内为null。 不理解这一点是导致所有混乱的原因。 我认为最简单的说法是未初始化的类在第一次引用时被“标记”为已初始化,所有其他引用都将其视为已初始化,这就是为什么会发生这种情况的原因。 - RalphChapin
对之前评论的一个更正:正如Dave Newton在JLS 12.4.2中所描述的那样,该类在被初始化时是“locked”的。其他线程将等待该类被初始化。然而,这并不影响本例,因为所有的操作都在一个线程中进行。 - RalphChapin

4
静态变量可以通过以下三种方式进行初始化,请选择您喜欢的任何一种:
  1. you can intialize it at the time of declaration
  2. or you can do by making static block eg:

    static {
            // whatever code is needed for initialization goes here
        }
    
  3. There is an alternative to static blocks — you can write a private static method

    class name {
        public static varType myVar = initializeVar();
    
        private static varType initializeVar() {
            // initialization code goes here
        }
    }
    

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