Почему Java не позволяет генерировать проверенное исключение из статического блока инициализации?

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


person missingfaktor    schedule 15.01.2010    source источник
comment
Какое исключение вы хотели бы создать в какой ситуации в статическом блоке?   -  person Kai Huppmann    schedule 15.01.2010
comment
Я не хочу делать ничего подобного. Я просто хочу знать, почему обязательно перехватывать проверенные исключения внутри статического блока.   -  person missingfaktor    schedule 15.01.2010
comment
Как вы ожидаете, что тогда будет обработано проверенное исключение? Если это вас беспокоит, просто повторно сгенерируйте пойманное исключение с помощью throw new RuntimeException(Telling message, e);   -  person Thorbjørn Ravn Andersen    schedule 15.01.2010
comment
@ThorbjørnRavnAndersen Java фактически предоставляет тип исключения для этой ситуации: docs.oracle.com/javase/6/docs/api/java/lang/   -  person smp7d    schedule 26.09.2012
comment
@ smp7d См. ответ kevinarpe ниже и его комментарий от StephenC. Это действительно крутая функция, но в ней есть ловушки!   -  person Benj    schedule 27.03.2017


Ответы (8)


Потому что невозможно обработать эти проверенные исключения в вашем источнике. У вас нет никакого контроля над процессом инициализации, и блоки static{} не могут быть вызваны из вашего источника, чтобы вы могли окружить их с помощью try-catch.

Поскольку вы не можете обработать любую ошибку, указанную проверенным исключением, было решено запретить создание статических блоков проверенных исключений.

Статический блок не должен генерировать исключения checked, но по-прежнему позволяет генерировать непроверенные исключения/исключения времени выполнения. Но в соответствии с вышеуказанными причинами вы также не сможете справиться с этим.

Подводя итог, можно сказать, что это ограничение не позволяет (или, по крайней мере, затрудняет) разработчику создавать что-то, что может привести к ошибкам, после которых приложение не сможет восстановиться.

person Kosi2801    schedule 15.01.2010
comment
На самом деле этот ответ неверен. Вы МОЖЕТЕ создавать исключения в статическом блоке. Чего вы не можете сделать, так это разрешить исключению checked распространяться из статического блока. - person Stephen C; 15.01.2010
comment
Вы МОЖЕТЕ обработать это исключение, если вы выполняете динамическую загрузку класса самостоятельно, с помощью Class.forName(..., true, ...); Согласитесь, такое не часто встретишь. - person LadyCailin; 19.12.2012
comment
static { throw new NullPointerExcpetion() } — это тоже не скомпилируется! - person Kirill Bazarov; 26.03.2014
comment
@KirillBazarov класс со статическим инициализатором, который всегда приводит к исключению, не будет компилироваться (потому что зачем?). Оберните этот оператор throw в предложение if, и все готово. - person Kallja; 22.04.2014
comment
static{ try { System.out.println("Test.enclosing_method()"); } catch (Exception e) { throw new NullPointerException(); } } Я бросаю нулевой указатель в статический, это компилируется. - person Ravisha; 09.04.2015
comment
@Ravisha Исключение NullPointerException разрешено, потому что это исключение RuntimeException, а не проверенное исключение. - person Kosi2801; 10.04.2015
comment
тогда почему это не компилируется static{ throw new NullPointerException(); } - person Ravisha; 11.04.2015
comment
@Ravisha, потому что в этом случае у инициализатора нет шансов нормально завершиться в любом случае. При использовании try-catch println может не вызывать никаких исключений, и поэтому у инициализатора есть шанс завершиться без исключений. Это безусловный результат исключения, которое делает его ошибкой компиляции. Для этого см. JLS: документы. oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Но компилятор все равно может быть обманут, добавив в вашем случае простое условие: static { if(1 < 10) { throw new NullPointerException(); } } - person Kosi2801; 15.04.2015
comment
Если я могу генерировать исключения во время выполнения в статическом блоке, то почему public class X { static { throw new NullPointerException(); } } не компилируется? - person Thamiar; 19.10.2017
comment
@Thamiar - потому что он выбрасывается безоговорочно и потому что вы не поймали его в статическом блоке. См. комментарии Kosi2801 выше. - person Stephen C; 12.12.2017

Вы можете обойти эту проблему, перехватив любое проверенное исключение и повторно сгенерировав его как непроверенное исключение. Этот непроверяемый класс исключений хорошо работает в качестве оболочки: 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);
        }
    }
}
person kevinarpe    schedule 08.03.2013
comment
@DK: Возможно, ваша версия Java не поддерживает этот тип предложения catch. Попробуйте: catch (Exception e) { вместо этого. - person kevinarpe; 04.05.2015
comment
Да, вы можете сделать это, но это действительно плохая идея. Непроверенное исключение помещает класс и любые другие классы, которые зависят от него, в состояние failed, которое может быть разрешено только путем выгрузки классов. Обычно это невозможно, и System.exit(...) (или эквивалент) — ваш единственный вариант, - person Stephen C; 19.10.2015
comment
@StephenC Можем ли мы думать, что если родительский класс не загружается, де-факто нет необходимости загружать его зависимые классы, поскольку ваш код не будет работать? Не могли бы вы привести пример случая, когда было бы необходимо в любом случае загрузить такой зависимый класс? Спасибо - person Benj; 27.03.2017
comment
Как насчет... если код попытается загрузить его динамически; например через Class.forName. - person Stephen C; 27.03.2017

Это должно выглядеть так (это не допустимый код 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 (который является интерфейсом со «скрытым» статическим инициализатором) должен был бы генерировать исключение или обрабатывать его — обработка исключений на интерфейсе? Лучше оставить как есть.

person Andreas Dolk    schedule 15.01.2010
comment
main может генерировать проверенные исключения. Понятно, что с ними не справиться. - person Mechanical snail; 15.01.2013
comment
@Mechanicalsnail: Интересный момент. В моей ментальной модели Java я предполагаю, что существует волшебный (по умолчанию) Thread.UncaughtExceptionHandler, прикрепленный к потоку, выполняющему main(), который печатает исключение с трассировкой стека в System.err, а затем вызывает System.exit(). В конце концов, ответ на этот вопрос, вероятно, таков: потому что так сказали разработчики Java. - person kevinarpe; 29.01.2017

Почему Java не позволяет генерировать проверенное исключение из статического блока инициализации?

Технически это можно сделать. Однако проверяемое исключение должно быть перехвачено внутри блока.

Фактическое ограничение Java заключается в том, что проверенному исключению не разрешено распространяться за пределы блока.

Технически также возможно разрешить распространение исключения unchecked из блока статического инициализатора1. Но это действительно плохая идея делать это преднамеренно! Проблема в том, что сама JVM перехватывает непроверенное исключение, упаковывает его и повторно выдает как ExceptionInInitializerError.

NB: это 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 выше, чтобы поймать ExceptionInInitializerError2.

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

Однако, если вы попытаетесь восстановить ExceptionInInitializerError, вы можете столкнуться с препятствием. Проблема в том, что перед тем, как выдать ошибку, JVM помечает класс, вызвавший проблему, как сбойный. Вы просто не сможете им пользоваться. Кроме того, любые другие классы, которые зависят от отказавшего класса, также перейдут в состояние отказа при попытке инициализации. Единственный путь вперед — выгрузить все неудавшиеся классы. Это может быть осуществимо для динамически загружаемого кода3, но в целом это не так.

1 – это ошибка компиляции, если статический блок безоговорочно выдает непроверенное исключение.
2 – вы можете перехватить его, зарегистрировав значение по умолчанию. uncaught обработчик исключений, но это не позволит вам восстановиться, потому что ваш основной поток не может запуститься.
3 - Если вы хотите восстановить неудавшиеся классы, вам нужно избавиться от загрузчика классов, который их загрузил.


Что послужило причиной такого дизайнерского решения?

Это сделано для того, чтобы защитить программиста от написания кода, который генерирует исключения, которые невозможно обработать... потому что у программиста нет возможности написать обработчик.

Как мы видели, исключение в статическом инициализаторе превращает типичное приложение в кирпич. Лучшее, что разработчики языка могут сделать, чтобы помочь программисту, это указать, что проверенный случай 1 является ошибкой компиляции. К сожалению, это нецелесообразно делать и для непроверенных исключений.


Итак, что вам делать, если ваш код должен генерировать исключения в статическом инициализаторе. В принципе, есть две альтернативы:

  • Если (полное!) восстановление из исключения внутри блока возможно, то сделайте это.

  • В противном случае реструктурируйте свой код, чтобы инициализация не происходила в статическом блоке инициализации (или в инициализаторах статических переменных). Поместите его в метод или конструктор, который можно вызывать из обычного потока.

person Stephen C    schedule 23.09.2018
comment
Существуют ли какие-либо общие рекомендации о том, как структурировать код, чтобы он не выполнял статическую инициализацию? - person MasterJoe; 05.07.2019
comment
Как звучат эти решения? stackoverflow.com/a/21321935/6648326 и stackoverflow.com/a/56575807/6648326 - person MasterJoe; 05.07.2019
comment
1) У меня нет. 2) Звучат плохо. Смотрите комментарии, которые я оставил на них. Но я только повторяю то, что сказал в своем ответе выше. Если вы прочтете и поймете мой ответ, вы поймете, что эти решения не являются решениями. - person Stephen C; 06.07.2019

Ознакомьтесь с спецификациями языка Java. : указано, что это ошибка времени компиляции, если статический инициализатор fail может завершиться внезапно с проверенным исключением.

person Laurent Etiemble    schedule 15.01.2010
comment
Однако это не отвечает на вопрос. он спросил почему это ошибка времени компиляции. - person Winston Smith; 15.01.2010
comment
Хм, так что генерировать любую RuntimeError должно быть возможно, потому что JLS упоминает только проверенные исключения. - person Andreas Dolk; 15.01.2010
comment
Правильно, но вы никогда не увидите трассировку стека. Вот почему вам нужно быть осторожным со статическими блоками инициализации. - person EJB; 15.01.2010
comment
@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... - person Konrad Höffner; 08.06.2012
comment
Часть Caused by показывает трассировку стека, которая вас, вероятно, больше интересует. - person LadyCailin; 19.12.2012

Поскольку никакой код, который вы пишете, не может вызвать статический блок инициализации, нет смысла выбрасывать проверенный exceptions. Если бы это было возможно, что бы делал jvm, когда выбрасываются проверенные исключения? Runtimeexceptions распространяются вверх.

person fastcodejava    schedule 27.02.2010
comment
Ну да, теперь я понимаю. Было очень глупо с моей стороны задавать такой вопрос. Но увы... Я не могу удалить его сейчас. :( Тем не менее, +1 за ваш ответ... - person missingfaktor; 27.02.2010
comment
@fast, на самом деле проверенные исключения НЕ преобразуются в RuntimeExceptions. Если вы сами пишете байт-код, вы можете создавать проверенные исключения внутри статического инициализатора, сколько душе угодно. JVM вообще не заботится о проверке исключений; это чисто конструкция языка Java. - person Antimony; 06.10.2012

Например: DispatcherServlet Spring (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());
    }
person pcdhan    schedule 13.06.2019
comment
Этот подход решает проблему, заключающуюся в том, что непроверенное исключение не может быть поймано. Вместо этого он переводит класс и любые другие классы, которые зависят от него, в невосстановимое состояние. - person Stephen C; 06.07.2019
comment
@StephenC - Не могли бы вы привести простой пример, в котором мы хотели бы иметь восстанавливаемое состояние? - person MasterJoe; 24.07.2019
comment
Гипотетически ... если вы хотите иметь возможность восстановиться после IOException, чтобы приложение могло продолжить работу. Если вы хотите это сделать, вы должны поймать исключение и фактически обработать его... а не генерировать непроверенное исключение. - person Stephen C; 24.07.2019

Я также могу скомпилировать исключение проверенного исключения ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
person user2775569    schedule 22.07.2016
comment
Да, но вы ловите его внутри статического блока. Вам не разрешено выбрасывать проверенное исключение из статического блока за его пределы. - person ArtOfWarfare; 06.12.2016