Ale*_*ian 11 java static initialization
在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);
}
}
Run Code Online (Sandbox Code Playgroud)
在使用该类的代码中,我执行以下操作:
Country country = Country.findByISO("RO");
Run Code Online (Sandbox Code Playgroud)
问题是我得到了一个NullPointerException因为map(allCountries)没有被初始化.如果我在static块中设置了断点,我可以看到地图正确填充,但就好像静态方法不知道正在执行的初始化程序.
谁能解释这种行为?
更新:我已经为代码添加了更多细节.它仍然不是1:1(那里有几个地图和更多逻辑),但我已经明确地查看了声明/引用,allCountries它们如上所列.
您可以在此处查看完整的初始化代码.
更新#2:我尽可能地简化了代码并将其写下来.实际代码在初始化程序之后具有静态变量声明.正如Jon在下面的答案中指出的那样,这导致它重置了引用.
我修改了帖子中的代码以反映这一点,因此对于发现问题的人来说更清楚.对大家的困惑感到抱歉.我只是想让每个人的生活更轻松:).
谢谢你的回答!
Jon*_*eet 29
在Java中调用类上的静态方法是否会触发静态初始化块来执行?
根据经验,我会说不.
你错了.
从JLS 第8.7节:
在类初始化时执行类中声明的静态初始化程序(第12.4.2节).与类变量的任何字段初始值设定项(第8.3.2节)一起,静态初始值设定项可用于初始化类的类变量.
JLS 第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
}
}
Run Code Online (Sandbox Code Playgroud)
您的问题出在您未向我们展示的代码的某些部分.我的猜测是你实际上声明了一个局部变量,如下所示:
static {
Map<String, Country> allCountries = new HashMap<String, Country>();
// Add entries to the map
}
Run Code Online (Sandbox Code Playgroud)
这隐藏了静态变量,将静态变量保留为null.如果是这种情况,只需将其更改为赋值而不是声明:
static {
allCountries = new HashMap<String, Country>();
// Add entries to the map
}
Run Code Online (Sandbox Code Playgroud)
编辑:有一点值得注意 - 虽然你已经init()作为静态初始化程序的第一行,如果你实际上在那之前做任何其他事情(可能在其他变量初始化程序中)调用另一个类,并且该类调用回到你的Country类,那么代码将被执行,而allCountries仍然是null.
编辑:好的,现在我们可以看到你的真实代码,我发现了问题.您的邮政编码包含:
private static Map<String, Country> allCountries;
static {
...
}
Run Code Online (Sandbox Code Playgroud)
但是你真正的代码有这个:
static {
...
}
private static Collection<Country> allCountries = null;
Run Code Online (Sandbox Code Playgroud)
这里有两个重要的区别:
这些组合使您陷入困境:变量初始值设定项并非都在静态初始化程序之前运行 - 初始化以文本顺序进行.
所以你要填充集合......然后将引用设置为null.
JLS的第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();
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
before = in init
after = after
leftDefault = in init
Run Code Online (Sandbox Code Playgroud)
所以解决方案是要么去除对null的显式赋值,要么将声明(以及因此初始化器)移动到静态初始化器之前,或者(我的首选项)两者.
| 归档时间: |
|
| 查看次数: |
16019 次 |
| 最近记录: |