Почему Java не позволяет генерировать проверенное исключение из статического блока инициализации? Что послужило причиной этого дизайнерского решения?
Почему Java не позволяет генерировать проверенное исключение из статического блока инициализации? Что послужило причиной этого дизайнерского решения?
Ответы:
Потому что невозможно обработать эти проверенные исключения в вашем источнике. У вас нет никакого контроля над процессом инициализации, и статические блоки {} не могут быть вызваны из вашего источника, чтобы вы могли окружить их try-catch.
Поскольку вы не можете обработать любую ошибку, указанную в проверенном исключении, было решено запретить выбрасывание статических блоков проверенных исключений.
Статический блок не должен выбрасывать проверенные исключения, но все же позволяет создавать непроверенные / исключительные ситуации времени выполнения. Но по вышеуказанным причинам вы также не сможете справиться с этим.
Подводя итог, можно сказать, что это ограничение не позволяет разработчику (или, по крайней мере, усложняет его) создавать что-то, что может привести к ошибкам, которые приложение не сможет восстановить.
static { if(1 < 10) { throw new NullPointerException(); } }
Вы можете обойти проблему, перехватывая любое проверенное исключение и выбрасывая его как исключение без проверки. Это бесконтрольно класс исключения хорошо работает в качестве оболочки: java.lang.ExceptionInInitializerError
.
Образец кода:
protected static class _YieldCurveConfigHelperSingleton {
public static YieldCurveConfigHelper _staticInstance;
static {
try {
_staticInstance = new YieldCurveConfigHelper();
}
catch (IOException | SAXException | JAXBException e) {
throw new ExceptionInInitializerError(e);
}
}
}
catch (Exception e) {
вместо.
System.exit(...)
(или эквивалентно) - ваш единственный выбор,
Это должно выглядеть следующим образом (это не допустимый код Java)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
а как бы объявление где вы его ловили? Проверенные исключения требуют отлова. Представьте себе некоторые примеры, которые могут инициализировать класс (или нет, потому что он уже инициализирован), и просто для того, чтобы привлечь внимание к сложности, которую он представляет, я поместил примеры в другой статический инициализатор:
static {
try {
ClassA a = new ClassA();
Class<ClassB> clazz = Class.forName(ClassB.class);
String something = ClassC.SOME_STATIC_FIELD;
} catch (Exception oops) {
// anybody knows which type might occur?
}
}
И еще одна неприятная вещь -
interface MyInterface {
final static ClassA a = new ClassA();
}
Вообразите, что у ClassA был статический инициализатор, выдававший проверенное исключение: в этом случае MyInterface (который является интерфейсом со «скрытым» статическим инициализатором) должен был бы выбросить исключение или обработать его - обработка исключений в интерфейсе? Лучше оставить все как есть.
main
может бросить проверенные исключения. Очевидно, что с этим нельзя справиться.
main()
который печатает исключение с трассировкой стека System.err
, а затем вызывает System.exit()
. В конце концов, ответ на этот вопрос, вероятно, таков: «потому что Java-дизайнеры так сказали».
Почему Java не позволяет генерировать проверенное исключение из статического блока инициализации?
Технически, вы можете сделать это. Однако проверенное исключение должно быть перехвачено в блоке. Проверенное исключение не может распространяться за пределы блока.
Технически, также возможно позволить неконтролируемому исключению распространяться из статического блока 1 инициализатора . Но это действительно плохая идея делать это сознательно! Проблема в том, что сама JVM перехватывает непроверенное исключение, оборачивает его и перебрасывает как ExceptionInInitializerError
.
NB: это 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(...)
, вы можете заключить вызов в a try
и поймать либо ExceptionInInitializerError
последующие, либо NoClassDefFoundError
.
Однако, если вы попытаетесь оправиться от него, ExceptionInInitializerError
вы можете столкнуться с контрольно-пропускным пунктом. Проблема заключается в том, что перед выдачей ошибки JVM помечает класс, вызвавший проблему, как «сбой». Вы просто не сможете его использовать. Кроме того, любые другие классы, которые зависят от отказавшего класса, также перейдут в сбойное состояние, если они попытаются инициализироваться. Единственный путь вперед - выгрузить все неудачные классы. Это может быть осуществимо для динамически загружаемого кода 3 , но в целом это не так.
1 - Это ошибка компиляции, если статический блок безоговорочно генерирует непроверенное исключение.
2. Вы можете перехватить его, зарегистрировав обработчик необработанных исключений по умолчанию, но это не позволит вам восстановиться, потому что ваш «основной» поток не может запуститься.
3 - Если вы хотите восстановить сбойные классы, вам нужно избавиться от загрузчика классов, который их загрузил.
Что послужило причиной этого дизайнерского решения?
Он защищает программиста от написания кода, который генерирует исключения, которые не могут быть обработаны!
Как мы уже видели, исключение в статическом инициализаторе превращает типичное приложение в кирпич. Лучше всего подумать, что разработчики языка могут сделать, это рассматривать проверенный случай как ошибку компиляции. (К сожалению, это не практично делать для непроверенных исключений.)
Итак, что вы должны сделать, если ваш код «нуждается» в генерации исключений в статическом инициализаторе. В основном, есть две альтернативы:
Если (полное!) Восстановление из исключения внутри блока возможно, то сделайте это.
В противном случае реструктурируйте свой код так, чтобы инициализация не происходила в статическом блоке инициализации (или в инициализаторах статических переменных).
Взгляните на спецификации языка Java : указано, что это ошибка времени компиляции, если статический сбой инициализатора может завершиться внезапно с проверенным исключением.
public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }
Вывод:Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: whathappenswhenastaticblockthrowsanexception at Main.<clinit>(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
Поскольку ни один код, который вы пишете, не может вызывать статический блок инициализации, бесполезно бросать флажок exceptions
. Если бы это было возможно, что бы сделал jvm, когда выбрасываются проверенные исключения? Runtimeexceptions
распространяются вверх.
Например: Spring DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) обрабатывает сценарий, который перехватывает проверенное исключение и выдает другое непроверенное исключение.
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
Я могу скомпилировать бросая проверенное исключение также ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}