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

时间:2012-03-13 22:09:37

标签: java static initialization

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

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

public class Country {
    static {
        init();
        List<Country> countries = DataSource.read(...); // get from a DAO
        addCountries(countries);
    }

    private static Map<String, Country> allCountries = null;

    private static void init() {
        allCountries = new HashMap<String, Country>();
    }

    private static void addCountries(List<Country> 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,因为地图(allCountries)未初始化。如果我在static块中设置断点,我可以看到地图正确填充,但就好像静态方法不知道正在执行的初始化程序。

任何人都可以解释这种行为吗?


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

您可以看到完整的初始化代码here

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

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

感谢您的回答!

4 个答案:

答案 0 :(得分:28)

  

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

     

根据经验,我会说不。

你错了。

来自JLS section 8.7

  

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

JLS的

Section 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
    }
}

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

static {
    Map<String, Country> allCountries = new HashMap<String, Country>();
    // Add entries to the map
}

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

static {
    allCountries = new HashMap<String, Country>();
    // Add entries to the map
}
编辑:有一点值得注意 - 尽管你已经将init()作为静态初始化程序的第一行,如果你实际在此之前做了其他事情(可能在其他变量初始值设定项)调用另一个类,并且该类将返回调用到您的Country类中,然后该代码将在allCountries仍为空时执行。

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

private static Map<String, Country> allCountries;
static {
    ...
}

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

static {
    ...
}
private static Collection<Country> allCountries = null;

这里有两个的重要区别:

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

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

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

JLS的

Section 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的显式赋值,将声明(因此初始化器)移到静态初始化器之前,或者(我的偏好)。

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

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

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

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

我跑了这个:

public class Country {
    private static Map<String, Country> allCountries;
    static {
        allCountries = new HashMap<String, Country>();
        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);
    }
}

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

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

答案 3 :(得分:0)

您的静态初始化程序块中是否有allCountries = new HashMap();?静态初始化程序块实际上是called上的class initialization