Java中的静态初始化器和静态方法

在Java中调用类上的静态方法是否会触发静态初始化块来执行?

根据经验,我会说不。 我有这样的事情:

public class Country { static { init(); List countries = DataSource.read(...); // get from a DAO addCountries(countries); } private static Map allCountries = null; private static void init() { allCountries = new HashMap(); } private static void addCountries(List countries) { for (Country country : countries) { if ((country.getISO() != null) && (country.getISO().length() > 0)) { allCountries.put(country.getISO(), country); } } } public static Country findByISO(String cc) { return allCountries.get(cc); } } 

在使用该类的代码中,我执行以下操作:

 Country country = Country.findByISO("RO"); 

问题是我得到一个NullPointerException因为map( allCountries )没有初始化。 如果我在static块中设置断点,我可以看到地图正确填充,但就好像静态方法不知道正在执行的初始化程序。

谁能解释这种行为?


更新 :我在代码中添加了更多细节。 它仍然不是1:1(那里有几个地图和更多逻辑),但我已经明确地查看了allCountries的声明/引用,它们如上所列。

您可以在此处查看完整的初始化代码。

更新#2 :我尽可能地简化了代码并将其写下来。 实际代码在初始化程序之后具有静态变量声明。 正如Jon在下面的答案中指出的那样,这导致它重置了引用。

我修改了post中的代码以反映这一点,因此对于发现问题的人来说更清楚。 对大家的困惑感到抱歉。 我只是想让每个人的生活更轻松:)。

谢谢你的回答!

在Java中调用类上的静态方法是否会触发静态初始化块来执行?

根据经验,我会说不。

你错了。

从JLS 第8.7节 :

在类初始化时执行类中声明的静态初始化程序(第12.4.2节)。 与类变量的任何字段初始值设定项(第8.3.2节)一起,静态初始值设定项可用于初始化类的类变量。

JLS 第12.4.1节规定:

类或接口类型T将在第一次出现以下任何一个之前立即初始化:

  • T是一个类,并且创建了T的实例。

  • T是一个类,调用T声明的静态方法。

  • 分配由T声明的静态字段。

  • 使用由T声明的静态字段,该字段不是常量变量(第4.12.4节)。

  • T是顶级类(第7.6节),并且执行在词典内嵌套在T(第8.1.3节)内的断言语句(第14.10节)。

这很容易显示:

 class Foo { static int x = 0; static { x = 10; } static int getX() { return x; } } public class Test { public static void main(String[] args) throws Exception { System.out.println(Foo.getX()); // Prints 10 } } 

您的问题出在您未向我们展示的代码的某些部分。 我的猜测是你实际上声明了一个局部变量,如下所示:

 static { Map allCountries = new HashMap(); // Add entries to the map } 

隐藏了静态变量,将静态变量保留为null。 如果是这种情况,只需将其更改为赋值而不是声明:

 static { allCountries = new HashMap(); // Add entries to the map } 

编辑:有一点值得注意 – 尽管你已经将init()作为静态初始化程序的第一行,如果你之前正在做其他任何事情(可能在其他变量初始化程序中),它会调用另一个类,并且该类调用 Country类,然后在allCountries仍然为null时执行该代码。

编辑:好的,现在我们可以看到你的真实代码,我发现了问题。 您邮政编码包含:

 private static Map allCountries; static { ... } 

但是你真正的代码有这个:

 static { ... } private static Collection allCountries = null; 

这里有两个重要的区别:

  • 变量声明发生静态初始化程序块之后
  • 变量声明包括对null的显式赋值

这些组合使您陷入困境 :变量初始值设定项并非都在静态初始化程序之前运行 – 初始化以文本顺序进行

所以你要填充集合……然后将引用设置为null。

JLS的第12.4.2节保证在初始化的第9步中:

接下来,按文本顺序执行类的类变量初始值设定项和类的静态初始值设定项,或接口的字段初始值设定项,就好像它们是单个块一样。

演示代码:

 class Foo { private static String before = "before"; static { before = "in init"; after = "in init"; leftDefault = "in init"; } private static String after = "after"; private static String leftDefault; static void dump() { System.out.println("before = " + before); System.out.println("after = " + after); System.out.println("leftDefault = " + leftDefault); } } public class Test { public static void main(String[] args) throws Exception { Foo.dump(); } } 

输出:

 before = in init after = after leftDefault = in init 

所以解决方案是要么去除对null的显式赋值, 要么将声明(以及因此初始化器)移动到静态初始化器之前,或者(我的首选项)两者。

加载类时将调用静态初始化程序,这通常是在首次“提及”时。 因此,如果这是第一次引用类,则调用静态方法确实会触发初始化程序。

你确定空指针exception来自allcountries.get() ,而不是来自get()返回的null Country吗? 换句话说,您确定哪个对象为空?

从理论上讲,静态块应该在classloader加载类时执行。

 Country country = Country.findByISO("RO"); ^ 

在您的代码中,它会在您第一次提到类Country时初始化(可能是上面的行)。

我跑了这个:

 public class Country { private static Map allCountries; static { allCountries = new HashMap(); allCountries.put("RO", new Country()); } public static Country findByISO(String cc) { return allCountries.get(cc); } } 

有了这个:

 public class Start { public static void main(String[] args){ Country country = Country.findByISO("RO"); System.out.println(country); } } 

一切正常。 你能发布错误的堆栈跟踪吗?

我想说问题在于静态块是在实际字段之前声明的。

你有allCountries = new HashMap(); 在静态初始化程序块中? 实际上,在类初始化时 调用静态初始化程序块。