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

135

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

missingfaktor
источник
Какое исключение вы бы хотели создать в такой ситуации в статическом блоке?
Кай Хуппманн
1
Я не хочу ничего такого делать. Я просто хочу знать, почему обязательно перехватывать проверенные исключения внутри статического блока.
фактор
Как вы ожидаете, что проверенное исключение будет обработано тогда? Если это вас беспокоит, просто сбросьте пойманную исключительную ситуацию с помощью throw new RuntimeException («Telling message», e);
Торбьерн Равн Андерсен
18
@ ThorbjørnRavnAndersen Java фактически предоставляет тип исключения для этой ситуации: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d
@ smp7d Смотрите ответ kevinarpe ниже и его комментарий от StephenC. Это действительно крутая фишка, но в ней есть ловушки!
Бендж

Ответы:

122

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

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

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

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

Kosi2801
источник
69
На самом деле, этот ответ неточный. Вы МОЖЕТЕ бросить исключения в статический блок. Чего вы не можете сделать, так это разрешить распространение проверенного исключения из статического блока.
Стивен С.
16
Вы МОЖЕТЕ обработать это исключение, если вы выполняете динамическую загрузку классов самостоятельно, с помощью Class.forName (..., true, ...); Конечно, это не то, с чем вы часто сталкиваетесь.
LadyCailin
2
static {throw new NullPointerExcpetion ()} - это также не будет компилироваться!
Кирилл Базаров
4
@KirillBazarov класс со статическим инициализатором, который всегда приводит к исключению, не будет компилироваться (потому что зачем это?). Оберните это выражение броска в предложении if, и все готово.
Калля
2
@Ravisha, потому что в этом случае инициализатор не сможет нормально завершить работу в любом случае. При использовании try-catch 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);
        }
    }
}
kevinarpe
источник
1
@DK: Возможно, ваша версия Java не поддерживает этот тип предложения catch. Попробуйте: catch (Exception e) {вместо.
Кевинарпе
4
Да, вы можете сделать это, но это действительно плохая идея. Непроверенное исключение помещает класс и все другие классы, которые зависят от него, в состояние сбоя, которое может быть разрешено только путем выгрузки классов. Это, как правило, невозможно, и System.exit(...)(или эквивалентно) - ваш единственный выбор,
Стивен С.
1
@StephenC можем ли мы думать, что если «родительский» класс не загружается, то де-факто нет необходимости загружать его зависимые классы, так как ваш код не будет работать? Не могли бы вы привести пример случая, когда было бы необходимо в любом случае загрузить такой зависимый класс? Спасибо
Бендж
Как насчет ... если код пытается загрузить его динамически; например, через Class.forName.
Стивен С
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-дизайнеры так сказали».
Кевинарпе
7

Почему 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(...), вы можете заключить вызов в a tryи поймать либо ExceptionInInitializerErrorпоследующие, либо NoClassDefFoundError.

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

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

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

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


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

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

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


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

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

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

Стивен С
источник
Существуют ли общие рекомендации о том, как структурировать код, чтобы он не выполнял статическую инициализацию?
MasterJoe
Как звучат эти решения? stackoverflow.com/a/21321935/6648326 и stackoverflow.com/a/56575807/6648326
MasterJoe
1
1) у меня их нет 2) Они звучат плохо. Смотрите комментарии, которые я оставил на них. Но я только повторяю то, что сказал в своем ответе выше. Если вы прочитаете и поймете мой ответ, вы поймете, что эти «решения» не являются решениями.
Стивен С
4

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

Лоран Etiemble
источник
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...
Конрад Хеффнер
Часть «Причины» показывает трассировку стека, которая вас, вероятно, больше интересует.
LadyCailin
2

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

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

Например: 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());
    }
pcdhan
источник
1
При таком подходе проблема в том, что непроверенное исключение не может быть перехвачено. Вместо этого он переводит класс и любые другие классы, которые зависят от него, в неисправимое состояние.
Стивен К
@StephenC - Не могли бы вы привести простой пример, в котором мы хотели бы иметь восстанавливаемое состояние?
MasterJoe
Гипотетически ... если вы хотите иметь возможность восстановления из IOException, чтобы приложение могло продолжаться. Если вы хотите сделать это, то вы должны перехватить исключение и обработать его ... не выбрасывать непроверенное исключение.
Стивен С.
-5

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

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
user2775569
источник
3
Да, но вы ловите это в статическом блоке. Вы не можете бросить проверенное исключение из статического блока наружу.
ArtOfWarfare