Java中的静态初始化器和静态方法 [英] Static Initializers And Static Methods In Java

查看:136
本文介绍了Java中的静态初始化器和静态方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

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

The problem is that I get a NullPointerException because the map (allCountries) is not initialized. 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.

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

Can anyone explain this behavior?

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

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.

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

You can see the full initialization code here.

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

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.

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

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 :).

感谢您的回答!

推荐答案


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

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

根据经验,我会说不。

你错了。

来自JLS 第8.7节


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

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.1节 JLS声明:


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

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


  • T是一个类,并且创建了一个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声明的静态字段。

A static field declared by T is assigned.

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

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

T是顶级类(第7.6节),并且执行在词典中嵌套在T(第8.1.3节)内的断言语句(第14.10节)。

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

Thi s很容易显示:

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
}

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

That hides the static variable, leaving the static variable 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
}

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

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.

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

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

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

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

此处有两个重要差异:


  • 变量声明在静态初始化程序块之后发生

  • 变量声明包括对null

  • The variable declaration occurs after the static initializer block
  • The variable declaration includes an explicit assignment to 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.

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

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

第12.4.2节在初始化的第9步保证它:

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


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

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

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.

这篇关于Java中的静态初始化器和静态方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆