Существуют ли какие-либо гарантии в JLS о порядке выполнения статических инициализационных блоков?

Интересно, надежно ли использовать конструкцию вроде:

private static final Map<String, String> engMessages;
private static final Map<String, String> rusMessages;

static {
    engMessages = new HashMap<String, String> () {{
        put ("msgname", "value");
    }};
    rusMessages = new HashMap<String, String> () {{
        put ("msgname", "значение");
    }};
}

private static Map<String, String> msgSource;

static {
    msgSource = engMessages;
}

public static String msg (String msgName) {
    return msgSource.get (msgName);
}

Есть ли вероятность, что я получу NullPointerException, потому что блок инициализации msgSource будет выполнен до блока, который инициализирует engMessages?

(о том, почему я не выполняю инициализацию msgSource в конце верхнего блока init: просто вопрос вкуса, я сделаю это, если описанная конструкция ненадежна)

Ответ 1

Да, блоки статического инициализатора гарантированно выполняются в текстовом порядке.

Из JLS, раздел 12.4.1:

Цель состоит в том, что класс или тип интерфейса имеет набор инициализаторов, которые помещают его в согласованное состояние и что это состояние является первым состоянием, которое наблюдается другими классами. Статические инициализаторы и инициализаторы переменных класса выполняются в текстовом порядке и могут не ссылаться на переменные класса, объявленные в классе, объявления которых появляются после использования, даже если эти переменные класса находятся в области видимости (§8.3 0,3). Это ограничение предназначено для обнаружения во время компиляции большинства круговых или иначе искаженных инициализаций.

И из 12.4.2:

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

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