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

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

Ответы [ 8 ]

107 голосов
/ 15 января 2010

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

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

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

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

64 голосов
/ 08 марта 2013

Вы можете обойти проблему, перехватывая любое проверенное исключение и выбрасывая его как исключение без проверки. Этот непроверенный класс исключений хорошо работает как оболочка: 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);
        }
    }
}
19 голосов
/ 15 января 2010

Это должно выглядеть следующим образом (это не действительный код 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 голосов
/ 23 сентября 2018

Почему 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 - Если вы хотите восстановить сбойные классы, вам нужно избавиться от загрузчика классов, который их загрузил.


В чем причина этого дизайнерского решения?

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

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


ОК, так что вы должны делать, если ваш код "нуждается" в генерации исключений в статическом инициализаторе. В основном, есть две альтернативы:

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

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

4 голосов
/ 15 января 2010

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

2 голосов
/ 27 февраля 2010

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

0 голосов
/ 13 июня 2019

Например: 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());
    }
0 голосов
/ 22 июля 2016

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

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