簡體   English   中英

Java中的靜態初始化器和靜態方法

[英]Static Initializers And Static Methods In 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因為map( allCountries )沒有初始化。 如果我在static塊中設置斷點,我可以看到地圖正確填充,但就好像靜態方法不知道正在執行的初始化程序。

誰能解釋這種行為?


更新 :我已經為代碼添加了更多細節。 它仍然不是1:1(那里有幾個地圖和更多邏輯),但我已經明確地查看了allCountries的聲明/引用,它們如上所列。

您可以在此處查看完整的初始化代碼。

更新#2 :我盡可能地簡化了代碼並將其寫下來。 實際代碼在初始化程序之后具有靜態變量聲明。 正如Jon在下面的答案中指出的那樣,這導致它重置了引用。

我修改了帖子中的代碼以反映這一點,因此對於發現問題的人來說更清楚。 對大家的困惑感到抱歉。 我只是想讓每個人的生活更輕松:)。

謝謝你的回答!

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

您的問題出在您未向我們展示的代碼的某些部分。 我的猜測是你實際上聲明了一個局部變量,如下所示:

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

隱藏了靜態變量,將靜態變量保留為null。 如果是這種情況,只需將其更改為賦值而不是聲明:

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

編輯:有一點值得一提的-盡管你已經有了init()作為你的靜態初始化程序的第一行,如果你實際上在做任何事情之前,然后(可能在其他變量初始化),其中要求到另一個階級,該類調用您的Country類,然后該代碼將在allCountries仍然為null時執行。

編輯:好的,現在我們可以看到你的真實代碼,我發現了問題。 郵政編碼包含:

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

但是你真正的代碼有這個:

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

這里有兩個重要的區別:

  • 變量聲明發生靜態初始化程序塊之后
  • 變量聲明包括對null的顯式賦值

這些組合使您陷入困境 :變量初始值設定項並非都在靜態初始化程序之前運行 - 初始化以文本順序進行

所以你要填充集合......然后將引用設置為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();
    }
}

輸出:

before = in init
after = after
leftDefault = in init

所以解決方案是要么去除對null的顯式賦值, 要么將聲明(以及因此初始化器)移動到靜態初始化器之前,或者(我的首選項)兩者。

加載類時將調用靜態初始化程序,這通常是在首次“提及”時。 因此,如果這是第一次引用類,則調用靜態方法確實會觸發初始化器。

你確定空指針異常來自allcountries.get() ,而不是來自get()返回的null Country嗎? 換句話說,您確定哪個對象為空?

從理論上講,靜態塊應該在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);
    }
}

一切正常。 你可以發布錯誤的堆棧跟蹤嗎?

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

你有allCountries = new HashMap(); 在靜態初始化程序塊中? 實際上,在類初始化時 調用靜態初始化程序塊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM