2012-03-13 8 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, я вижу, что карта заполняется правильно, но это похоже на то, что статический метод не знает о выполняемом инициализаторе.

Может ли кто-нибудь объяснить это поведение?


Update: Я добавил больше деталей к коду. Это все еще не 1: 1 (там есть несколько карт и больше логики), но я явно посмотрел объявления/ссылки allCountries, и они перечислены выше.

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

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

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

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

+2

Вы можете указать код, с которым вы инициализируете карту? – Tom

+1

Кстати, у вас отсутствует возвращаемый тип метода findByISO() в вашем примере. –

ответ

26

Вызывает ли статический метод для класса в Java инициировать статические блоки инициализации для выполнения?

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

Вы ошибаетесь.

От JLS section 8.7:

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

Section 12.4.1 из JLS гласит:

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

  • Т представляет собой класс и создается экземпляр 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 
} 

That скрывает статическую переменную, оставив статическую переменную нуль.Если это так, то просто изменить его на уступки вместо декларации:

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

EDIT: Одна точка стоит отметить - хотя у вас есть init() в самой первой строке вашего статического инициализатора, если вы делаете на самом деле что-то еще до этого (возможно, в других инициализаторах переменных), которое вызывает другой класс, и этот класс вызывает назад в ваш класс Country, тогда этот код будет выполнен, а allCountries по-прежнему равен нулю.

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

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

Но ваш реального код имеет следующее:

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

Есть два важных различий здесь:

  • Переменная декларация происходит после статический инициатор alizer блок
  • Переменная декларация включает в себя явное присваивание обнулить

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

Итак, вы заполняете коллекцию ... и затем устанавливаете ссылку на null.

Section 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 

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

+0

Спасибо за разъяснение. Я проверил ссылки на карту, и все в порядке, я ссылаюсь на статическую переменную, не объявляя локальную. Я опубликовал больше кода, чтобы обеспечить понимание. –

+0

@AlexCiminian: Ну, это определенно не ваш настоящий код - метод 'findByISO' не имеет типа возврата. Я все еще уверен, что проблема в вашем коде ... хотя у меня была другая идея. Будет редактировать. –

+0

'init()' фактически является первой строкой инициализатора и не вызывает другого класса, имеющего ссылки на страну. Он просто инициализирует несколько владельцев внутри класса «Страна». Вы можете увидеть полный код в ссылке hastebin, которую я опубликовал в своем редактировании (в конце сообщения). –

2

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

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

+0

Да, я уверен, что исключение запускается из-за нулевой карты. –

2

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

Country country = Country.findByISO("RO"); 
^ 

В вашем коде оно инициализируется при первом упоминании страны класса (возможно, строки выше).

Я побежал это:

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

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

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

0

У вас есть allCountries = new HashMap(); в вашем статическом блоке инициализации? Статический блок инициализатора фактически равен called по адресу class initialization.

Смежные вопросы