Почему Java не позволяет генерировать проверенное исключение из статического блока инициализации?
Технически, вы можете сделать это. Однако проверенное исключение должно быть перехвачено в блоке. Проверенное исключение не может распространяться вне блока.
Технически, возможно также, что непроверенное исключение распространяется из блока статического инициализатора 1 . Но это действительно плохая идея делать это сознательно! Проблема в том, что сама JVM перехватывает непроверенное исключение, упаковывает его и выбрасывает как ExceptionInInitializerError
.
Примечание: это Error
не обычное исключение. Вы не должны пытаться оправиться от него.
В большинстве случаев исключение не может быть поймано:
public class Test {
static {
int i = 1;
if (i == 1) {
throw new RuntimeException("Bang!");
}
}
public static void main(String[] args) {
try {
// stuff
} catch (Throwable ex) {
// This won't be executed.
System.out.println("Caught " + ex);
}
}
}
$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
at Test.<clinit>(Test.java:5)
Вы нигде не можете поместить try ... catch
в вышеприведенный список, чтобы поймать ExceptionInInitializerError
2 .
В некоторых случаях вы можете его поймать. Например, если вы инициировали инициализацию класса, вызвав Class.forName(...)
, вы можете заключить вызов в try
и перехватить либо ExceptionInInitializerError
, либо последующий NoClassDefFoundError
.
Однако, если вы попытаетесь восстановить из ExceptionInInitializerError
, вы можете столкнуться с контрольно-пропускным пунктом. Проблема в том, что перед выдачей ошибки JVM помечает класс, который вызвал проблему, как «сбой». Вы просто не сможете его использовать. Кроме того, любые другие классы, которые зависят от отказавшего класса, также перейдут в сбойное состояние, если они попытаются инициализироваться. Единственный путь вперед - выгрузить все неудачные классы. То, что может быть возможным для динамически загружаемого кода 3 , но в целом это не так.
1 - это ошибка компиляции, если статический блок безусловно генерирует непроверенное исключение.
2 - Вы могли бы иметь возможность перехватить его, зарегистрировав обработчик необработанных исключений по умолчанию, но это не позволит вам восстановиться, потому что ваш «основной» поток не может запуститься.
3 - Если вы хотите восстановить сбойные классы, вам нужно избавиться от загрузчика классов, который их загрузил.
В чем причина этого дизайнерского решения?
Это защита программиста от написания кода, который выдает исключения, которые не могут быть обработаны !
Как мы уже видели, исключение в статическом инициализаторе превращает типичное приложение в кирпич. Лучше всего подумать, что разработчики языка могут сделать, это рассматривать проверенный случай как ошибку компиляции. (К сожалению, это также нецелесообразно делать для непроверенных исключений.)
ОК, так что вы должны делать, если ваш код "нуждается" в генерации исключений в статическом инициализаторе. В основном, есть две альтернативы:
Если (полное!) Восстановление из исключения внутри блока возможно, то сделайте это.
В противном случае реструктурируйте код, чтобы инициализация не происходила в статическом блоке инициализации (или в инициализаторах статических переменных).