是否存在Java不立即初始化静态字段的情况?

时间:2014-12-28 10:26:43

标签: java static-initialization

在一个更大的项目中,我遇到了静态字段初始化的奇怪行为(至少对我的理解)。据我了解,所有静态字段都应该在程序启动时初始化,这意味着当开始使用非静态字段时,不应该有任何非初始化的静态字段(更确切地说,所有静态分配“field = .. “应该已经完成​​了。”

以下代码不是MWE,因为它符合我的预期,但它基本上就是我在更大的背景下所做的。我无法创建一个导致同样问题的小例子。

运行此代码时:

import java.util.HashSet;

public class FB {
    private static final HashSet<String> collection = new HashSet<>();
    public static final String foo = bar("item");

    public static String bar(String newItem) {
        collection.add(newItem);
        System.out.println("Yes, I've been invoked, and I currently store this: " + collection);
        return newItem;
    }

    public static void main(String[] args) {
    }
}

输出是(因为Java首先初始化静态字段'collection',然后通过调用bar(。)初始化foo):

Yes, I've been invoked, and I currently store this: [item]

到目前为止,这么好。在实际项目中,我正在做这个(虽然foo和bar(。)在不同的类中),但是在我实际使用foo的值之前不调用bar(。)。 (至少这种情况发生在五个中的一个案例中 - 它们都以与上面所示相同的方式创建。其他四个工作正常。)是否有任何情况会导致Java表现得像这样?

我已经看过这些讨论,但它们似乎没有解决我的问题:

When are static variables are initialized?
Why static fields are not initialized in time?
Java Static Field Initialization

我意识到,当交换foo和collection的位置时,方法invokation无法工作,因为当foo初始化时,collection不会被初始化(或者更确切地说,初始化为null?)。 (老实说,我不确定静态字段在位于不同的类中时的初始化顺序,因此这可能是问题的根源。)但这会导致

Exception in thread "main" java.lang.ExceptionInInitializerError

而不仅仅是不调用bar(。)。

如果需要,我可以提供有关真实项目的更多详细信息,但到目前为止我还不知道还有什么可能有意义的。 (抱歉模糊描述,但这是我到目前为止所有的。)

5 个答案:

答案 0 :(得分:4)

静态变量由JVM类加载器实例化,并由类的每个实例共享。

public class StaticVars {

    static int i = 3;

    public static void main( String[] args ) {
        System.out.println( "Called at Runtime: " + getStaticVar() );
        System.out.println( "Called via it's static member: " + i );
    }


    static int getStaticVar() {
        return i;
    }

    static {
        int i = 1;
        System.out.println( "JVM ClassLoaded: " + i );
    }

    static {
        int i = 2;
        System.out.println( "Second JVM ClassLoaded: " + i);
    }

}

此外,在引用dognoseNPE时,如果您在初始化之前尝试引用静态变量,则会得到Illegal Forward Reference Error,因为静态字段是按顺序初始化的。以下限制适用于静态fields静态方法不会以相同方式检查。

  

<强> 8.3.2.3. Restrictions on the use of Fields during Initialization

     
    
        
  • 成员的声明只有在成员是类或接口C 的实例(分别是静态)字段并且满足以下所有条件时才需要以文本方式显示

  •     
  • 用法发生在C的实例(分别是静态)变量初始值设定项或C的实例(分别是静态)初始值设定项中。

  •     
  • 用法不在作业的左侧。

  •     
  • 用法是通过一个简单的名称。

  •     
  • C是封闭用法的最里面的类或接口。

  •     
  

更多信息:: Illegal forward reference error for static final fields

答案 1 :(得分:2)

  

开始使用非static字段时,不应该有任何未初始化的static字段(更确切地说,应该执行所有静态分配field = ...)。

一般来说,情况就是如此。但是,可以构建一个不存在的例子。 The following code打印出来

static_obj=null
instance_obj=4

意味着static_obj尚未在instance_obj时间之前初始化:

class Ideone
{
    static {
        new Ideone();
    }   

    public static final Object static_obj = new Integer(42);
    public final Object instance_obj = new Integer(4);

    public Ideone() {
        System.out.printf("static_obj=%s\n", static_obj);
        System.out.printf("instance_obj=%s\n", instance_obj);
    }

    public static void main(String[] args) {
    }
}

答案 2 :(得分:1)

如果您编译上面的类,您可以看到在静态初始化块中调用了该条。

static <clinit>()V
  L0
    LINENUMBER 4 L0
    NEW java/util/HashSet
    DUP
    INVOKESPECIAL java/util/HashSet.<init> ()V
    PUTSTATIC FB.collection : Ljava/util/HashSet;
  L1
    LINENUMBER 5 L1
    LDC "item"
    INVOKESTATIC FB.bar (Ljava/lang/String;)Ljava/lang/String;
    PUTSTATIC FB.foo : Ljava/lang/String;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

FB中查看不完整的初始化类的唯一方法是在静态块中。

e.g。说FB.<clinit>()调用另一个类,然后在初始化之前使用FB.foo,它会看到null而不是调用bar()

答案 3 :(得分:1)

静态字段按声明的顺序初始化。因此,如果您要将字段的顺序更改为:

import java.util.HashSet;

    public class FB {
        public static final String foo = bar("item");
        private static final HashSet<String> collection = new HashSet<>();

        public static String bar(String newItem) {
            collection.add(newItem);
            System.out.println("Yes, I've been invoked, and I currently store this: " + collection);
            return newItem;
        }

        public static void main(String[] args) {
        }
    }

当您尝试访问尚未初始化的集合时,您将在NullpointerException - 方法中收到bar。 (因为您在初始化第一个变量的过程中发生了对bar()的调用)

Exception in thread "main" java.lang.ExceptionInInitializerError                                

Caused by: java.lang.NullPointerException                                                       

        at HelloWorld.bar(HelloWorld.java:14)                                                   

        at HelloWorld.<clinit>(HelloWorld.java:10)    

答案 4 :(得分:1)

类的静态字段和静态init块并不总是初始化/执行。如果您不使用类或不加载它,则不会发生。例如 - 假设你有:

class Test
{
    static
    {
        System.out.println("Test");
    }

    public static int a = 4; 
}

如果不使用Test类,则不会执行静态块。因此,您必须例如将类实例化为init static fields并调用静态init块:

Test t = new Test();

或使用任何静态的东西,如:

System.out.println(Test.a); // this will trigger 'initialisation'

毕竟 - 如果你不在你的代码中使用静态数据 - 为什么JVM会用它做任何事情 - 认为它是合理的优化; - )。

使用JDBC的另一个例子 - 你必须调用它一次才能让驱动程序初始化&#39;本身 - 即。执行所有静态的,稍后需要:

Class.forName( "com.mysql.jdbc.Driver" ).newInstance();

BTW:你可以这样开始你的收藏:

private static final HashSet<String> collection = new HashSet<String>() {{add("item");}};