Чому Java не дозволяє викинути перевірений виняток із блоку статичної ініціалізації? Що було причиною цього дизайнерського рішення?
Чому Java не дозволяє викинути перевірений виняток із блоку статичної ініціалізації? Що було причиною цього дизайнерського рішення?
Відповіді:
Тому що неможливо обробити ці перевірені винятки у вашому джерелі. Ви не маєте ніякого контролю над процесом ініціалізації, і статичні блоки {} не можуть бути викликані з вашого джерела, щоб ви могли оточити їх за допомогою пробного лову.
Оскільки ви не можете впоратися з будь-якою помилкою, вказаною перевіреним винятком, було вирішено заборонити викидати статичні блоки перевірених винятків.
Статичний блок не повинен викидати перевірені винятки, але все ж дозволяє викидати неперевірені / тривалість виконання. Але, відповідно до вищезазначених причин, ви також не зможете впоратися з цими.
Підсумовуючи це, це обмеження перешкоджає (або принаймні ускладнює) розробника не будувати щось, що може призвести до помилок, через які додаток не зможе відновитись.
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 ловить неперевірений виняток і загортає його та повторно запускає як a 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's 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
}
}