为什么不允许在Java实例初始化块中抛出异常?

为什么不允许在Java实例初始化块中抛出异常?,java,Java,当我试图在实例初始化(而非类初始化)块中引发异常时,我得到错误: initializer must be able to complete normally 尽管Java自己做,但为什么它是不被允许的 下面的示例创建了四个类。由于算术异常,类A在实例化过程中失败。这可以通过捕获来处理。对于B,也是如此,它会因NullPointerException而失败。但是当我试图在C中自己抛出一个NullPointerException时,程序不会编译。当我试图定义自己的RuntimeException时

当我试图在实例初始化(而非类初始化)块中引发异常时,我得到错误:

initializer must be able to complete normally
尽管Java自己做,但为什么它是不被允许的

下面的示例创建了四个类。由于算术异常,类
A
在实例化过程中失败。这可以通过
捕获
来处理。对于
B
,也是如此,它会因NullPointerException而失败。但是当我试图在
C
中自己抛出一个NullPointerException时,程序不会编译。当我试图定义自己的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();
    }
}

Java被设计成具有最小的特性,只有在有很好的理由这样做时,才会增加复杂性。Java不问;为什么不呢;我真的需要支持吗?(即使这样,有时也不会;)

初始化器块的代码必须插入到每个构造函数中,编译器知道该块无法正常完成,编译器发现很难为其生成代码

可以让编译器编译这段代码,但它不太可能有任何用处


在这种特殊情况下,这对您没有帮助,但了解

必须声明选中的异常,并且无法在静态或实例初始化器块中声明选中的异常

相反,您可以捕获并处理或包装选中的异常。(或使用技巧,重新播放)

初始值设定项必须能够正常完成

意味着必须有一个可能的代码路径不会引发异常。你的例子无条件地抛出,因此被拒绝。在其他示例中,静态分析还不足以确定它们在所有情况下都会抛出错误

比如说,

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);
    }
}
公共类静态抛出{
静态int foo=0;
{{if(Math.sin(3)<0.5){抛出新的算术异常(“Heya”);}否则{foo=3;}}}
公共静态void main(字符串[]args){
StaticThrow t=新的StaticThrow();
System.out.println(StaticThrow.foo);
}
}
编译,并在运行时抛出

线程“main”java.lang.arithmetricException中的异常:Heya 在StaticThrow。(StaticThrow.java:3) 位于StaticThrow.main(StaticThrow.java:5) 这意味着该实例将永远无法正确初始化。应该有一些条件可以正确初始化实例。e、 g

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

如果抛出的异常是选中的异常,则必须将其添加到构造函数的
throws
子句中。e、 g

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

    public MyObject() throws Exception {

    }
}

实际上,您可以在初始化块中抛出异常,但如果异常是选中的,则必须用“throws”关键字标记所有构造函数

如果总是抛出异常,则会出现编译错误,但这样做是完全合法的:

福班{

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

public Foo() throws Exception {

}
}

希望这能澄清一些事情。

来自

实例初始值设定项中的代码可能不会返回。除了在 对于匿名内部类,实例初始值设定项可能会抛出 仅当已检查的异常显式 在类中每个构造函数的throws子句中声明。 另一方面,匿名内部类中的实例初始值设定项, 可以抛出任何异常


这将导致其他语句显然无法访问,Java试图禁止这些语句。

这在Java语言规范(JavaSE7)的第三部分中有介绍

如果实例初始值设定项无法完成,则为编译时错误 通常()

14.21定义了无法达到的含义。特别注意

非空块中的每一条语句都不是开关 当S前面的语句可以完成时,块是可到达的 通常

break、continue、return或throw语句无法完成 通常

更复杂的分析是可能的(并且仍然可能产生警告),但是这些规则是可以理解的、一致可实现的,并且不会特别限制语言的未来发展

那么,我们为什么要拒绝带有(肯定)不可访问语句的程序呢?因为它们几乎肯定代表bug(在完成的代码中)。(
if
语句的行为特别支持不可靠的条件编译。)

没有任何不可访问的语句,那么为什么实例初始值设定项必须能够正常完成(这不是构造函数支持不可实例化类的要求)?因为这需要非局部分析,而Java为了保持合理的简单性并没有这样做,并且在维护过程中可能会删除语句,或者只是重新排列代码顺序


可能值得注意的是,有一种观点认为,这种相对简单的分析以及定义的分配规则使Java变得过于复杂。

以及如何创建未经检查的异常?未经检查的异常扩展了
RuntimeException
。没有好的方法可以接受一个本来会被检查的异常,比如
IOException
,并将其取消检查。但是,如果您创建了一个直接或间接扩展
RuntimeException
的新类型,那么您可以抛出它的一个实例,而无需添加
throws
子句。编译器对代码的分析不够深入,无法看到它总是抛出。在您的示例中,您将
{{{throw something;}}
放在静态初始化器中,这是显而易见的。尝试一些永远正确但不明显的东西,
{{if(Math.sin(3)<0.5){throw new arithmetricexception;}else{whatever;}}}}
。谢谢<代码>如果(真)抛出…就足够了
{{
    if(1 == 1) {
        throw new Exception();
    }
}}

public Foo() throws Exception {

}