【问题标题】:Static Initializers And Static Methods In JavaJava中的静态初始化器和静态方法
【发布时间】:2012-03-30 09:09:51
【问题描述】:

在 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:我尝试尽可能简化代码并即时将其写下来。实际代码在初始化器之后有静态变量声明。正如乔恩在下面的答案中指出的那样,这导致它重置了参考。

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

感谢您的回答!

【问题讨论】:

  • 你能显示初始化地图的代码吗?
  • 顺便说一句,您在示例中缺少 findByISO() 方法的返回类型。

标签: java static initialization


【解决方案1】:

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

您的问题在于您没有向我们展示的代码的某些部分。我的猜测是你实际上是在声明一个局部变量,像这样:

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

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

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

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

编辑:好的,现在我们可以看到您的真实代码,我发现了问题。你的 post 代码有这样的:

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

【讨论】:

  • 感谢您的澄清。我检查了对地图的引用,它们很好,我引用的是静态变量,而不是声明本地变量。我发布了更多代码以提供见解。
  • @AlexCiminian:这绝对不是你的真实代码——findByISO 方法没有返回类型。我仍然确定问题出在您的代码中......尽管我有另一个想法。将编辑。
  • init() 实际上是初始化程序的第一行,它不会调用任何其他引用 Country 的类。它只是初始化 Country 类中的几个持有者。您可以在我在编辑中发布的 hastebin 链接中看到完整的代码(在帖子的末尾)。
  • @AlexCiminian:堆栈跟踪失败时究竟是什么样的?如果您可以将其包含在帖子中,那将真的很有帮助。如果您可以发布一个简短但完整的示例来演示该问题,那将更有帮助:)
  • 我也是,这是因为在静态初始化程序之后,变量被设置为空。顺序很重要,刚刚想通了。 :) 非常感谢您的支持!
【解决方案2】:

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

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】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 是的,我确信异常会因为空映射而被触发。
      猜你喜欢
      • 1970-01-01
      • 2016-06-28
      • 2012-01-01
      • 2023-03-26
      • 1970-01-01
      • 2012-07-22
      • 2013-04-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多