Чому Java не дозволяє викинути перевірений виняток із блоку статичної ініціалізації?


135

Чому Java не дозволяє викинути перевірений виняток із блоку статичної ініціалізації? Що було причиною цього дизайнерського рішення?


Який виняток ви б хотіли кинути, в якій ситуації статичного блоку?
Кай Хупман

1
Я не хочу нічого подібного робити. Мені просто хочеться знати, чому обов’язково ловити перевірені винятки всередині статичного блоку.
зниклийфактор

Як би ви могли очікувати, що тоді перевіряється виняток? Якщо вас це турбує, просто перекиньте спійманий виняток із закиданням нового RuntimeException ("Телінг повідомлення", е);
Thorbjørn Ravn Andersen

18
@ ThorbjørnRavnAndersen Java фактично надає тип винятку
smp7d

@ smp7d Дивіться відповідь kevinarpe нижче та її коментар від StephenC. Це дійсно класна функція, але в ній є пастки!
Бендж

Відповіді:


122

Тому що неможливо обробити ці перевірені винятки у вашому джерелі. Ви не маєте ніякого контролю над процесом ініціалізації, і статичні блоки {} не можуть бути викликані з вашого джерела, щоб ви могли оточити їх за допомогою пробного лову.

Оскільки ви не можете впоратися з будь-якою помилкою, вказаною перевіреним винятком, було вирішено заборонити викидати статичні блоки перевірених винятків.

Статичний блок не повинен викидати перевірені винятки, але все ж дозволяє викидати неперевірені / тривалість виконання. Але, відповідно до вищезазначених причин, ви також не зможете впоратися з цими.

Підсумовуючи це, це обмеження перешкоджає (або принаймні ускладнює) розробника не будувати щось, що може призвести до помилок, через які додаток не зможе відновитись.


69
Власне, ця відповідь неточна. МОЖЕТЕ кидати винятки в статичний блок. Що ви не можете зробити, це дозволити розповсюдженню вивіреного винятку із статичного блоку.
Stephen C

16
Ви МОЖЕТЕ обробляти цей виняток, якщо ви самі робите динамічне завантаження класу за допомогою Class.forName (..., true, ...); Зрозуміло, це не те, що трапляється дуже часто.
LadyCailin

2
static {киньте новий NullPointerExcpetion ()} - це також не буде компілюватися!
Кирило Базаров

4
@KirillBazarov клас зі статичним ініціалізатором, який завжди призводить до виключення, не буде компілюватися (бо навіщо це робити?). Загорніть цей випадок у програму if-if, і ви добре підете.
Калля

2
@Ravisha, оскільки в такому випадку ініціалізатор не може нормально завершитись у будь-якому випадку. З пробним уловом може не бути винятком, викинутим println, і тому ініціалізатор має шанс завершити без винятку. Це безумовний результат виключення, що робить його помилкою компіляції. Про це дивіться JLS: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Але компілятор може все-таки обдурити, додавши у вашому випадку просту умову:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801

67

Ви можете подолати проблему, схопивши будь-який перевірений виняток і повторно скидаючи його як неперевірений виняток. Це безконтрольно клас виключення добре працює в якості оболонки: 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);
        }
    }
}

1
@DK: Можливо, ваша версія Java не підтримує цей тип застереження. Спробуйте: catch (Exception e) {натомість.
kevinarpe

4
Так, ви можете це зробити, але це дійсно погана ідея. Неперевірений виняток ставить клас та будь-які інші класи, які залежать від його невдалого стану, які можна вирішити лише шляхом вивантаження класів. Це, як правило, неможливо, і System.exit(...)(або еквівалент) - ваш єдиний варіант,
Stephen C

1
@StephenC чи можемо ми подумати, що якщо "батьківський" клас не вдасться завантажити, фактично непотрібно завантажувати його залежні класи, оскільки ваш код не працюватиме? Чи можете ви надати приклад випадку, коли потрібно було б все-таки завантажити такий залежний клас? Спасибі
Бендж

Як щодо ... якщо код намагається завантажити його динамічно; наприклад, через Class.forName.
Стівен C

21

Він повинен виглядати наступним чином (це НЕ дійсний код 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 (який є інтерфейсом зі «прихованим» статичним ініціалізатором) повинен був би викинути виняток або обробити його - обробка виключень в інтерфейсі? Краще залиште так, як є.


7
mainможе кидати перевірені винятки. Очевидно, що з ними не можна впоратися.
Механічний равлик

@Mechanicalsnail: Цікавий момент. У моїй ментальній моделі Java, я припускаю, що існує "магічний" (за замовчуванням) Thread.UncaughtExceptionHandler, приєднаний до потоку запущеного, main()який друкує виняток із слідом стека System.err, а потім викликає System.exit(). Зрештою, відповідь на це питання, ймовірно, «тому, що так сказали дизайнери Java».
kevinarpe

7

Чому 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у вищезазначеному, щоб зловити ExceptionInInitializerError2 .

У деяких випадках його можна зловити. Наприклад, якщо ви ініціювали ініціалізацію класу за допомогою виклику Class.forName(...), ви можете укласти виклик у a tryі виловити або наступний, ExceptionInInitializerErrorабо наступний NoClassDefFoundError.

Однак, якщо ви спробуєте відновитись із ExceptionInInitializerErrorвас, ви можете зіткнутися з блокпостом. Проблема полягає в тому, що перед видачею помилки JVM позначає клас, який спричинив проблему, як "невдалий". Ви просто не зможете ним користуватися. Крім того, будь-які інші класи, які залежать від невдалого класу, також перейдуть у невдалий стан, якщо вони спробують ініціалізуватися. Єдиний шлях вперед - це вивантажити всі невдалі класи. Це може бути можливим для динамічно завантаженого коду 3 , але загалом це не так.

1 - Це помилка компіляції, якщо статичний блок беззастережно кидає неперевірений виняток.

2 - Можливо, ви зможете перехопити це, зареєструвавши обробник винятків за замовчуванням за замовчуванням, але це не дозволить відновитись, тому що ваш "основний" потік не може запуститися.

3 - Якщо ви хочете відновити невдалі класи, вам потрібно буде позбутися завантажувача класів, який їх завантажив.


Що було причиною цього дизайнерського рішення?

Це захистити програміста від написання коду, який викидає винятки, які неможливо обробити!

Як ми бачили, виняток у статичному ініціалізаторі перетворює типову програму в цеглу. Найкраще подумати, що мовні дизайнери могли зробити - це поводитися з перевіреним випадком як помилкою компіляції. (На жаль, це також не практично для неперевірених винятків.)


Гаразд, то що робити, якщо ваш код "потребує", щоб викинути винятки в статичний ініціалізатор. В основному, є дві альтернативи:

  1. Якщо можливе (повне!) Відновлення після виключення в блоці, зробіть це.

  2. В іншому випадку реструктуруйте свій код, щоб ініціалізація не відбулася в статичному блоці ініціалізації (або в ініціалізаторах статичних змінних).


Чи є якісь загальні рекомендації щодо структуризації коду, щоб він не робив статичної ініціалізації?
MasterJoe2


1
1) У мене немає. 2) Вони погано звучать. Подивіться коментарі, які я залишив до них. Але я лише повторюю те, що я сказав у своєму відповіді вище. Якщо ви прочитаєте та зрозумієте мою відповідь, ви дізнаєтесь, що «рішення» - це не рішення.
Стівен C

4

Погляньте на специфікації мови Java : зазначається, що це помилка часу компіляції, якщо статичний ініціалізатор не в змозі завершити різко за допомогою перевіреного винятку.


5
Однак це не відповідає на питання. він запитав, чому це помилка часу компіляції.
Вінстон Сміт

Хм, тому викидати будь-який RuntimeError має бути можливо, оскільки JLS лише згадує перевірені винятки.
Андреас Долк

Це правильно, але ви ніколи не побачите як стек-трек. Ось чому вам потрібно бути обережними з блоками статичної ініціалізації.
EJB

2
@EJB: Це неправильно. Я щойно спробував це, і наступний код дав мені візуальну стежку: 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...
Konrad Höffner

Частина "Викликана" показує стек стека, який вас, мабуть, більше цікавить.
LadyCailin

2

Оскільки жоден написаний вами код не може викликати статичний блок ініціалізації, не корисно кидати перевірений exceptions. Якби це було можливо, що б зробив jvm, коли кинуті перевірені винятки? Runtimeexceptionsрозмножуються.


1
Ну, так, я зараз розумію річ. Мені було дуже нерозумно ставити таке питання. Але на жаль ... я не можу її видалити зараз. :( Тим не менш, +1 для вашої відповіді ...
пропав фактор

1
@fast, Власне, перевірені винятки НЕ перетворюються на RuntimeExceptions. Якщо ви пишете байт-код самостійно, ви можете кидати перевірені винятки всередині статичного ініціалізатора до вмісту вашого серця. JVM взагалі не піклується про перевірку виключень; це суто конструкція мови Java.
Сурма

0

Наприклад: 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());
    }

1
Це підходить до проблеми, що неперевірений виняток не може бути зловлений Натомість він переводить клас та будь-які інші класи, які залежать від нього, у стан, який не можна відновити.
Стівен C

@StephenC - Скажіть, будь ласка, простий приклад, у якому ми хотіли б мати стан, який можна відновити?
MasterJoe2

Гіпотетично ... якщо ви хочете отримати можливість відновитися після IOException, щоб програма могла продовжуватись. Якщо ви хочете це зробити, то ви повинні зловити виняток і фактично впоратися з ним ... а не кидати неперевірений виняток.
Стівен С

-5

Я можу скласти кидання перевіреного Винятку Також….

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}

3
Так, але ви ловите це в статичному блоці. Вам не дозволяється кидати перевірений виняток зсередини статичного блоку назовні.
ArtOfWarfare
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.