在一个更大的项目中,我遇到了静态字段初始化的奇怪行为(至少对我的理解)。据我了解,所有静态字段都应该在程序启动时初始化,这意味着当开始使用非静态字段时,不应该有任何非初始化的静态字段(更确切地说,所有静态分配“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(。)。
如果需要,我可以提供有关真实项目的更多详细信息,但到目前为止我还不知道还有什么可能有意义的。 (抱歉模糊描述,但这是我到目前为止所有的。)
答案 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);
}
}
此外,在引用dognose和NPE时,如果您在初始化之前尝试引用静态变量,则会得到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");}};