简体   繁体   English

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

[英]Static Initializers And Static Methods In Java

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 NullPointerException because the map ( allCountries ) is not initialized. 问题是我得到一个NullPointerException因为map( allCountries )没有初始化。 If I set up breakpoints in the static block I can see the map getting populated correctly, but it's as if the static method has no knowledge of the initializer being executed. 如果我在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 allCountries and 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. 更新#2 :我尽可能地简化了代码并将其写下来。 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. 正如Jon在下面的答案中指出的那样,这导致它重置了引用。

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! 谢谢你的回答!

Does calling a static method on a class in Java trigger the static initalization blocks to get executed? 在Java中调用类上的静态方法是否会触发静态初始化块来执行?

Empirically, I'd say no. 根据经验,我会说不。

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). 在类初始化时执行类中声明的静态初始化程序(第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. 与类变量的任何字段初始值设定项(第8.3.2节)一起,静态初始值设定项可用于初始化类的类变量。

Section 12.4.1 of 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将在第一次出现以下任何一个之前立即初始化:

  • T is a class and an instance of T is created. T是一个类,并且创建了T的实例。

  • T is a class and a static method declared by T is invoked. T是一个类,并且调用由T声明的静态方法。

  • A static field declared by T is assigned. 分配由T声明的静态字段。

  • A static field declared by T is used and the field is not a constant variable (§4.12.4). 使用由T声明的静态字段,该字段不是常量变量(第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是顶级类(第7.6节),并且执行在词典内嵌套在T(第8.1.3节)内的断言语句(第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 guess is 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 hides the static variable, leaving the static variable null. 隐藏了静态变量,将静态变量保留为null。 If this is the case, just change it to an assignment instead 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 actually doing anything else before then (possibly in other variable initializers) which calls out to another class, and that class calls back into your Country class, then that code will be executed while allCountries is still null. 编辑:有一点值得一提的-尽管你已经有了init()作为你的静态初始化程序的第一行,如果你实际上在做任何事情之前,然后(可能在其他变量初始化),其中要求到另一个阶级,该类调用您的Country类,然后该代码将在allCountries仍然为null时执行。

EDIT: Okay, now we can see your real code, I've found the problem. 编辑:好的,现在我们可以看到你的真实代码,我发现了问题。 Your post code has this: 邮政编码包含:

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

But your real code has this: 但是你真正的代码有这个:

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

There are two important differences here: 这里有两个重要的区别:

  • The variable declaration occurs after the 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.2 of 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 either to get rid of the explicit assignment to null, or to move the declarations (and therefore initializers) to before the static initializer, or (my preference) both. 所以解决方案是要么去除对null的显式赋值, 要么将声明(以及因此初始化器)移动到静态初始化器之前,或者(我的首选项)两者。

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 Country returned by get() ? 你确定空指针异常来自allcountries.get() ,而不是来自get()返回的null Country吗? In other words, are you certain which object is null? 换句话说,您确定哪个对象为空?

Theoretically, static block should get executed by the time classloader loads the class. 从理论上讲,静态块应该在classloader加载类时执行。

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. 我想说问题在于静态块是在实际字段之前声明的。

Do you have allCountries = new HashMap(); 你有allCountries = new HashMap(); in your static initializer block? 在静态初始化程序块中? The static initializer block is actually called upon class initialization . 实际上,在类初始化时 调用静态初始化程序块。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM