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


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

7 115

7 ответов:

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

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

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

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

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

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

посмотри Спецификации Языка 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 в выше, чтобы поймать ExceptionInInitializerError2.

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

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

1 - это ошибка компиляции, если статический блок безоговорочно бросает исключение непроверенное.

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

3-Если вы хотите восстановить неудачные классы, вам нужно будет избавиться от загрузчика классов, который их загрузил.


какова была причина этого проектного решения?

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


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

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

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

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

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

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