Исключения при инициализации

Поведение зависит от того, инициализируется класс или экземпляр.

Instance initializer

Исключение из экземплярного поля/блока прерывает конструктор. Объект не возвращается вызывающему коду.

final class Dictionary {
    private final Map<String, String> values;
 
    {
        // unchecked ошибка прервёт создание экземпляра
        if (System.getProperty("dict.mode") == null) {
            throw new IllegalStateException("dict.mode is required");
        }
    }
 
    Dictionary(Path path) throws IOException {
        values = load(path); // checked exception объявлена конструктором
    }
}

Checked exception из инициализатора должна быть перехвачена или совместима с throws всех применимых конструкторов. Частично построенный объект может утечь только если код сам опубликовал this, чего делать не следует.

Static initializer

Static-блок не имеет throws, поэтому checked exception надо обработать или завернуть. Если инициализация завершается ошибкой:

final class Config {
    static final Properties VALUES;
 
    static {
        try (InputStream in = Config.class.getResourceAsStream("/app.properties")) {
            if (in == null) {
                throw new IOException("resource not found");
            }
            var properties = new Properties();
            properties.load(in);
            VALUES = properties;
        } catch (IOException error) {
            throw new ExceptionInInitializerError(error);
        }
    }
}

Первый активный пользователь класса получает ExceptionInInitializerError (если исходная причина не была Error). Класс считается ошибочно инициализированным; дальнейшие обращения тем же загрузчиком обычно приводят к NoClassDefFoundError.

Практический вывод

Не выполнять в static-инициализации сеть, подключение к БД и восстановимую работу. Лучше явный объект конфигурации/фабрика: тогда ошибку можно обработать, повторить и протестировать.

На собеседовании

Можно ли объявить throws у static-блока? Нет.

Что происходит с объектом при ошибке instance initializer? Конструктор не завершается, ссылка вызывающему коду не возвращается.

Можно ли повторно инициализировать класс после ошибки? Не тем же определением класса/загрузчиком обычным обращением.

Почему после первой ошибки виден NoClassDefFoundError? JVM помнит, что инициализация этого класса завершилась неуспешно.

Связано: Блоки инициализации, Работа с исключениями в Java, Class loader.