Статические инициализаторы и статические методы в Java - PullRequest
9 голосов
/ 14 марта 2012

Приводит ли вызов статического метода к классу в 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, и они такие же, как перечисленные выше.

Вы можете увидеть полный код инициализации здесь .

Обновление # 2 : Я попытался максимально упростить код и записал его на лету. Фактический код имеет объявление статической переменной после инициализатора. Это заставило его сбросить ссылку, как указал Джон в ответе ниже.

Я изменил код в своем посте, чтобы отразить это, чтобы он был понятнее людям, которые находят вопрос. Извините за путаницу всех. Я просто пытался сделать жизнь каждого человека проще.

Спасибо за ваши ответы!

Ответы [ 4 ]

28 голосов
/ 14 марта 2012

Приводит ли вызов статического метода к классу в Java запуск статических блоков инициализации для выполнения?

Эмпирически, я бы сказал, нет.

Вы 'повторно неправильно.

Из раздела JLS 8.7 :

Статический инициализатор, объявленный в классе, выполняется при инициализации класса (§12.4.2),Вместе с любыми инициализаторами полей для переменных класса (§8.3.2) статические инициализаторы могут использоваться для инициализации переменных класса класса.

Раздел 12.4.1 изСостояния JLS:

Класс T или интерфейсный тип T будут инициализированы непосредственно перед первым появлением любого из следующего:

  • T является классом исоздается экземпляр T.

  • T является классом, и вызывается статический метод, объявленный T.

  • Статическое поле, объявленноеПрисвоено T.

  • Используется статическое поле, объявленное T, и это поле не является постоянной переменной (§4.12.4).

  • T является классом верхнего уровня (§7.6), и выполняется оператор assert (§14.10), лексически вложенный в T (§8.1.3).

Этолегко показать:

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() в качестве самогопервой строке вашего статического инициализатора, если вы на самом деле делаете что-то еще до этого (возможно, в других инициализаторах переменных), которое вызывает другой класс, и этот класс вызывает back в вашCountry class, тогда этот код будет выполняться, пока allCountries по-прежнему равен нулю.

EDIT: Хорошо, теперь мы можем увидеть ваш реальный код, я обнаружил проблему.Ваш post код имеет это:

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

Но ваш real код имеет это:

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

Есть two важные отличия здесь:

  • Объявление переменной происходит после статического блока инициализации
  • Объявление переменной включает явное присвоение нулю

Комбинация из них запутывает вас: инициализаторы переменных не запускаются до статического инициализатора - инициализация происходит в текстовом порядке .

Итак, вы заполняетеcollection ... и затем установка ссылки на ноль.

Раздел 12.4.2 JLS гарантирует это на шаге 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

Таким образом, решение - или , чтобы избавиться от явного присвоения нулю, или для перемещения объявлений (и, следовательно, инициализаторов) до статического инициализатора, или(мои предпочтения) оба.

2 голосов
/ 14 марта 2012

Теоретически, статический блок должен выполняться к тому времени, когда загрузчик классов загружает класс.

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

и все работало правильно. Вы можете опубликовать трассировку стека ошибки?

Я бы сказал, что проблема заключается в том, что статический блок объявлен перед фактическим полем.

2 голосов
/ 14 марта 2012

Статический инициализатор будет вызываться при загрузке класса, что обычно происходит при первом «упоминании».Так что вызов статического метода действительно вызовет инициализатор, если это первый раз, когда на класс ссылаются.

Вы уверены, что исключение нулевого указателя происходит из allcountries.get(), а не из нулевого Countryвернул get()?Другими словами, вы уверены, , какой объект является нулевым?

0 голосов
/ 14 марта 2012

Есть ли у вас allCountries = new HashMap(); в вашем блоке статического инициализатора?Статический блок инициализатора фактически называется при инициализации класса .

...