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

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

initializer must be able to complete normally

Почему это не допустимо, хотя Java делает это сам?

В следующем примере создаются четыре класса. Класс A не работает во время создания экземпляра из-за исключения ArithmeticException. Это можно и обрабатывать с помощью catch. То же самое для B, которое не выполняется с помощью исключения NullPointerException. Но когда я пытаюсь выполнить исключение NullPointerException самостоятельно, как в C, программа не компилируется. И я получаю ту же ошибку, когда пытаюсь определить собственное RuntimeException, как в D. Итак:

Как я могу сделать то же самое, что и сама Java?

// -*- compile-command: "javac expr.java && java expr"; -*-

class expr
{
    class A
    {
        int y;
        {{ y = 0 / 0; }}
    }

    class B
    {
        Integer x = null;
        int y;
        {{ y = x.intValue(); }}
    }

    class C
    {
        {{ throw new NullPointerException(); }}
    }

    class Rex extends RuntimeException {}

    class D
    {
        {{ throw new Rex(); }}
    }

    void run ()
    {
        try { A a = new A(); }
        catch (Exception e) { System.out.println (e); }

        try { B b = new B(); }
        catch (Exception e) { System.out.println (e); }

        try { C c = new C(); }
        catch (Exception e) { System.out.println (e); }

        try { D d = new D(); }
        catch (Exception e) { System.out.println (e); }
    }

    public static void main (String argv[])
    {
        expr e = new expr();
        e.run();
    }
}

Ответ 1

инициализатор должен иметь возможность нормально завершить

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

Например,

public class StaticThrow {
    static int foo = 0;
    {{ if (Math.sin(3) < 0.5) { throw new ArithmeticException("Heya"); } else { foo = 3; } }}
    public static void main(String[] args) {
        StaticThrow t = new StaticThrow();
        System.out.println(StaticThrow.foo);
    }
}

компилируется, а при запуске throw

Exception in thread "main" java.lang.ArithmeticException: Heya
        at StaticThrow.<init>(StaticThrow.java:3)
        at StaticThrow.main(StaticThrow.java:5)

Ответ 2

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

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

Компилятор может быть создан для компиляции этого кода, но вряд ли он будет полезен.


Это не поможет вам в этом конкретном случае, но полезно знать, что.....

Проверяемые исключения должны быть объявлены и не существует способа объявить проверенное исключение в статическом или инициализационном блоке экземпляра.

Вместо этого вы можете ловить и обрабатывать, или обернуть проверенное исключение. (Или используя трюки, перебросьте его)

Ответ 3

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

Вы получите компиляционную ошибку, если ваше исключение всегда будет выбрано, но что-то вроде этого совершенно легально:

класс Foo {

{{
    if(1 == 1) {
        throw new Exception();
    }
}}

public Foo() throws Exception {

}

}

Надеюсь, это прояснит несколько вещей.

Ответ 4

{ throw new Rex(); }

Это означает, что экземпляр никогда не будет инициализирован должным образом. Должно быть какое-то условие, когда экземпляр может быть инициализирован правильно. например.

{ if(true) { throw new Rex(); } } //It doesn't complain here

Если выбрано исключение - исключение check-exception, вы должны добавить его в конструктор throws. например.

public class MyObject {
    { 
        //...
            throw new Exception();
        //...
    }

    public MyObject() throws Exception {

    }
}

Ответ 5

Из http://www.artima.com/designtechniques/initializationP.html

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

Ответ 6

Это описывается в разделе 8.6 Спецификации языка Java (Java SE 7).

Это ошибка времени компиляции, если инициализатор экземпляра не может завершить обычно (§14.21).

14.21 определяет, что значит быть недоступным. Обратите внимание, в частности,

Каждое другое утверждение S в непустом блоке, которое не является коммутатором блок доступен, если утверждение, предшествующее S, может завершиться в нормальном режиме.

и

Оператор break, continue, return или throw не может завершить в нормальном режиме.

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

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

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

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

Ответ 7

Это приведет к тому, что остальные утверждения, по-видимому, недоступны, которые Java пытается запретить.