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

是否存在Java不立即初始化静态字段的情况?,java,static-initialization,Java,Static Initialization,在一个更大的项目中,我在静态字段初始化时遇到了一种奇怪的行为(至少对我来说是这样)。据我所知,所有静态字段都应该在程序启动时初始化,这意味着当开始使用非静态字段时,不应该有任何未初始化的静态字段(更准确地说,应该执行所有静态赋值“field=…” 下面的代码不是MWE,因为它做了我期望它做的事情,但它本质上正是我在更大的上下文中所做的事情。我无法创建一个导致相同问题的较小示例 运行此代码时: import java.util.HashSet; public class FB { pri

在一个更大的项目中,我在静态字段初始化时遇到了一种奇怪的行为(至少对我来说是这样)。据我所知,所有静态字段都应该在程序启动时初始化,这意味着当开始使用非静态字段时,不应该有任何未初始化的静态字段(更准确地说,应该执行所有静态赋值“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) {
    }
}
到目前为止,一切顺利。在实际项目中,我正是这样做的(尽管foo和bar(.)在不同的类中),但是在我实际使用foo的值之前,bar(.)不会被调用。(至少五种情况中有一种会发生这种情况——它们都是以与上面所示相同的方式创建的。其他四种情况都很好。)是否有任何情况会导致Java出现这种行为

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



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

Exception in thread "main" java.lang.ExceptionInInitializerError
而不仅仅是不调用bar(.)


如果需要,我可以提供关于真实项目的更多细节,但到目前为止,我不知道还有什么可能感兴趣。(很抱歉,这里的描述很模糊,但到目前为止我只有这些。)

静态变量由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);
    }

}
此外,正如和引用的一样,如果您试图在静态变量初始化之前引用它,您将得到一个静态字段,因为静态字段是按顺序初始化的。以下限制适用于静态
字段
静态方法的检查方式不同

  • 仅当成员是类或接口C的实例(分别为静态)字段且以下所有条件均成立时,成员声明才需要在使用前以文本形式出现:

  • 该用法出现在C的实例(分别为静态)变量初始值设定项或C的实例(分别为静态)初始值设定项中

  • 用法不在作业的左侧

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

  • C是包含用法的最内层类或接口


更多信息::

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

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
static()V
L0
线路号4 L0
新的java/util/HashSet
重复
调用特定的java/util/HashSet。()V
PUTSTATIC FB.collection:Ljava/util/HashSet;
L1
线路号5 L1
最不发达国家“项目”
INVOKESTATIC FB.bar(Ljava/lang/String;)Ljava/lang/String;
PUTSTATIC FB.foo:Ljava/lang/String;
返回
MAXSTACK=2
最大局部变量=0
FB
中查看未完成初始化类的唯一方法是已经在静态块中

e、 例如,
FB。
调用另一个类,该类在初始化之前使用
FB.foo
,它将看到
null
,而不是调用
bar()

当开始使用非
静态
字段时,不应该有任何未初始化的
静态
字段(更准确地说,应该执行所有静态赋值
字段=…

一般来说,情况就是这样。但是,可以构造一个不存在的示例。打印出来

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) {
    }
}

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

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) {
        }
    }

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

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

    public static int a = 4; 
}
如果不使用测试类,则不会执行静态块。例如,您必须将一个类实例化为init静态字段并调用静态init块:

Test t = new Test();
或者使用任何静态的东西,例如:

System.out.println(Test.a); // this will trigger 'initialisation'
毕竟——如果在代码中不使用静态数据——为什么JVM要费心用它做任何事情——认为它是合理的优化;-)

另一个使用JDBC的示例—您必须调用此函数一次,以让驱动程序“init”本身—也就是说,执行所有静态的和以后需要的操作:

Class.forName( "com.mysql.jdbc.Driver" ).newInstance();
顺便说一句:你可以这样初始化你的收藏:

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

main
的第一行执行时,“程序启动”的确切定义是什么?(当然,在此之前可能会有代码运行,包括在所有静态初始值设定项运行之前。)如果不能提供最小的复制示例,我建议您使用调试器来查看初始化是如何发生的。我想,我的误解可能很简单。Eddie B写道,如果没有加载类,相应的静态字段也不会初始化。如果这是真的,它可以解释我观察到的行为。我会调查的!是的,如果不加载类,JVM甚至不知道类中有哪些静态变量。因此,任何与静态变量相关的事情都只能在类加载后发生。是的,这就是问题所在。谢谢大家对我的支持
Class.forName( "com.mysql.jdbc.Driver" ).newInstance();
private static final HashSet<String> collection = new HashSet<String>() {{add("item");}};