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

标签 java static initialization

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

根据经验,我会说不。我有这样的东西:

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 因为 map (allCountries) 没有初始化。如果我在 static block 中设置断点,我可以看到映射被正确填充,但就好像静态方法不知道正在执行的初始化程序一样。

谁能解释这种行为?


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

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

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

我修改了帖子中的代码以反射(reflect)这一点,因此发现问题的人会更清楚。对不起大家的困惑。我只是想让每个人的生活更轻松 :)。

感谢您的回答!

最佳答案

Does calling a static method on a class in Java trigger the static initalization blocks to get executed?

Empirically, I'd say no.

你错了。

来自 JLS section 8.7 :

A static initializer declared in a class is executed when the class is initialized (§12.4.2). Together with any field initializers for class variables (§8.3.2), static initializers may be used to initialize the class variables of the class.

Section 12.4.1 JLS 状态:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.

  • T is a class and a static method declared by T is invoked.

  • A static field declared by T is assigned.

  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

  • T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

这很容易显示:

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<String, Country> allCountries = new HashMap<String, Country>();
    // Add entries to the map
}

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

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

编辑:值得注意的一点 - 尽管您将 init() 作为静态初始化程序的第一行,但如果您实际上正在做任何其他事情在此之前(可能在其他变量初始值设定项中)调用另一个类,并且该类将 back 调用到您的 Country 类中,然后该代码将在 时执行allCountries 仍然为空。

编辑:好的,现在我们可以看到您的真实代码了,我已经找到了问题所在。你的帖子代码是这样的:

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

但是您的真实代码是这样的:

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

这里有两个重要的区别:

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

它们的组合让您一头雾水:变量初始化器并非都在静态初始化器之前运行 - 初始化以文本顺序发生。

所以您正在填充集合...然后将引用设置为 null。

Section 12.4.2 JLS 在初始化的第 9 步中保证了这一点:

Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.

演示代码:

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 的显式赋值,要么将声明(以及初始化器)移动到静态初始化器之前,或者 (我的偏好)两者都有。

关于java - Java 中的静态初始化器和静态方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9692977/

相关文章:

groovy - 如何使用 groovy mixin 添加静态方法

python - 在Python中实例化静态变量

c++ - 初始化字符串时额外的大括号

java - 在导航 View 中添加 ListView

java - 扩展实现另一个类的抽象类

java - 如何替换库中的默认属性

java - 根据属性将某些属性从 1 个列表复制到另一个列表(不同的属性合并)

c# - 如何在静态类中创建通用静态字典

c++ - 此语句如何在 c 或 c++ 中工作 `int k = (a++,++a);`

c++ - 在 C++ 中是否可以在初始化时覆盖虚拟方法?