2012-03-13 82 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); 
    } 
} 

,一切工作正常。你能發佈錯誤的堆棧跟蹤嗎?

我會說問題在於靜態塊在實際字段之前聲明的事實。