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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9692977/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 06:14:59  来源:igfitidea点击:

Static Initializers And Static Methods In Java

javastaticinitialization

提问by Alex Ciminian

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

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

Empirically, I'd say no. I have something like this:

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

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);
    }
}

In the code using the class, I do something like:

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

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

The problem is that I get a NullPointerExceptionbecause the map (allCountries) is not initialized. If I set up breakpoints in the staticblock I can see the map getting populated correctly, but it's as if the static method has no knowledge of the initializer being executed.

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

Can anyone explain this behavior?

谁能解释这种行为?



Update: I've added more detail to the code. It's still not 1:1 (there are several maps in there and more logic), but I've explicitly looked at the declarations/references of allCountriesand they are as listed above.

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

You can see the full initialization code here.

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

Update #2: I tried to simplify the code as much as possible and wrote it down on the fly. The actual code had the static variable declaration after the initializer. That caused it to reset the reference, as Jon pointed out in the answer below.

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

I modified the code in my post to reflect this, so it's clearer for people who find the question. Sorry about the confusion everyone. I was just trying to make everyone's life easier :).

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

Thanks for your answers!

感谢您的回答!

采纳答案by Jon Skeet

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

Empirically, I'd say no.

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

根据经验,我会说不。

You're wrong.

你错了。

From the JLS section 8.7:

从 JLS第 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.

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

Section 12.4.1of the JLS states:

JLS第 12.4.1 节规定:

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.

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

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

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

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

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

  • T 是顶级类(第 7.6 节),并且执行在词法上嵌套在 T(第 8.1.3 节)中的 assert 语句(第 14.10 节)。

This is easily shown:

这很容易显示:

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

Your problem is in some part of the code that you didn't show us. My guessis that you're actually declaring a local variable, like this:

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

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

That hidesthe static variable, leaving the static variable null. If this is the case, just change it to an assignmentinstead of a declaration:

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

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

EDIT: One point worth noting - although you've got init()as the very first line of your static initializer, if you're actuallydoing anything else before then (possibly in other variable initializers) which calls out to another class, and that class calls backinto your Countryclass, then that code will be executed while allCountriesis still null.

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

EDIT: Okay, now we can see your real code, I've found the problem. Your postcode has this:

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

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

But your realcode has this:

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

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

There are twoimportant differences here:

这里有两个重要的区别:

  • The variable declaration occurs afterthe static initializer block
  • The variable declaration includes an explicit assignment to null
  • 变量声明发生静态初始化块之后
  • 变量声明包括对 null 的显式赋值

The combination of those is messing you up: the variable initializers aren't all run before the static initializer - initialization occurs in textual order.

这些的组合让你一团糟:变量初始值设定项并非都在静态初始值设定项之前运行 - 初始化按文本顺序发生。

So you're populating the collection... and then setting the reference to null.

因此,您正在填充集合...然后将引用设置为 null。

Section 12.4.2of the JLS guarantees it in step 9 of the initialization:

JLS 的第 12.4.2 节在初始化的第 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.

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

Demonstration code:

演示代码:

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();
    }
}

Output:

输出:

before = in init
after = after
leftDefault = in init

So the solution is eitherto get rid of the explicit assignment to null, orto move the declarations (and therefore initializers) to before the static initializer, or (my preference) both.

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

回答by DNA

The static initializer will get called when the class is loaded, which is normally when it is first 'mentioned'. So calling a static method would indeed trigger the initializer if this is the first time that the class gets referenced.

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

Are you sure the null pointer exception is from the allcountries.get(), and not from a null Countryreturned by get()? In other words, are you certain whichobject is null?

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

回答by Jakub Zaverka

Theoretically, static block should get executed by the time classloader loads the class.

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

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

In your code, it is initialized the first time you mention the class Country (probably the line above).

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

I ran this:

我跑了这个:

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);
    }
}

with this:

有了这个:

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

and everything worked correctly. Can you post the stack trace of the error?

一切正常。您可以发布错误的堆栈跟踪吗?

I would say that the problem lies in the fact that the static block is declared before the actual field.

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

回答by Eugene Kuleshov

Do you have allCountries = new HashMap();in your static initializer block? The static initializer block is actually calledupon class initialization.

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