2012-03-13 81 views
8

在Java中调用静态方法是否触发静态初始化块以执行?经验上,我会说不。我有这样的事情:使用类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:我试图尽可能简化代码,并在飞行中写下它。实际的代码在初始化程序后有静态变量声明。正如乔恩在下面的答案中指出的那样,这导致它重置参考。

我修改了我的帖子中的代码以反映这一点,所以对于发现问题的人来说更清楚。对不起每个人都很困惑。我只是想让每个人的生活更轻松:)。

感谢您的回答!

+2

你可以显示你初始化地图的代码吗? – Tom 2012-03-13 22:12:54

+1

顺便说一句,你在示例中缺少findByISO()方法的返回类型。 – 2012-03-13 22:18:10

回答

26

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

静态变量,使静态变量空。如果是这种情况,只需将其更改为分配代替声明:

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

编辑:值得注意的一点 - 尽管你有init()为您的静态初始化的第一行,如果你实际上之前做任何事情(可能在其他变量初始值设定项)调用另一个类,该类调用返回到您的Country类,那么代码将被执行,而allCountries仍然为空。

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

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

但你真正代码有这样的:

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

2点这里重要的区别:

  • 变量声明时之后静态启动alizer块
  • 变量声明包含一个明确的赋值为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,移动的声明(并因此初始化器)到静态初始化器之前,或(我的首选项)。

+0

感谢您的澄清。我已经检查过对地图的引用,他们很好,我引用了静态变量,而不是声明本地变量。我发布了更多代码来提供洞察力。 – 2012-03-13 22:35:28

+0

@AlexCiminian:那绝对不是你真正的代码 - 'findByISO'方法没有返回类型。我仍然确定这个问题在你的代码中,尽管我有另外一个想法。将编辑。 – 2012-03-13 22:37:56

+0

'init()'实际上是初始化程序的第一行,它不会调用任何其他具有对Country的引用的类。它只是初始化'Country'类中的几个持有者。您可以在我的编辑(在帖子末尾)发布的hastebin链接中看到完整的代码。 – 2012-03-13 22:45:29

2

当类被加载时,静态初始化器将被调用,通常当它被首先'提及'时。所以调用一个静态方法确实会触发初始化器,如果这是该类第一次被引用。

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

+0

是的,我相信这个异常会因为空图而被触发。 – 2012-03-13 22:39:20

2

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

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

,一切工作正常。你能发布错误的堆栈跟踪吗?

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