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

时间:2012-11-23 16:30:12

标签: java

当我尝试在实例初始化(而不是类初始化)块中抛出异常时,我收到错误:

initializer must be able to complete normally

为什么不允许Java自己呢?

以下示例创建了四个类。由于ArithmeticException,类A在实例化期间失败。这可以通过catch来处理。 B失败并出现NullPointerException。但是当我尝试在C中自己抛出NullPointerException时,程序无法编译。当我尝试在D中定义自己的RuntimeException时,我得到了同样的错误。所以:

我怎样才能像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();
    }
}

7 个答案:

答案 0 :(得分:14)

  

初始化程序必须能够正常完成

表示必须存在不会引发异常的可能代码路径。您的示例无条件抛出,因此被拒绝。在其他示例中,静态分析不足以确定它们也会抛出所有情况。

例如,

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);
    }
}

编译,并在运行时抛出

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

答案 1 :(得分:5)

Java旨在具有最小的功能,并且只有在有充分理由这样做时才会添加复杂性。 Java不问;为什么不呢,它问道;我真的需要支持吗? (有时甚至没有;)

初始化程序块的代码必须插入到每个构造函数中,并且编译器知道的块无法正常完成,因为编译器发现生成代码的条件太难了。

编译器可以编译此代码,但它不太可能有用。


在这个特定的情况下,它不会帮助你,但知道.....

很有用

必须声明已检查的异常,并且无法在静态或实例初始化块中声明已检查的异常。

相反,您可以捕获并处理或包装已检查的异常。 (或使用技巧,重新抛出它)

答案 2 :(得分:2)

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

如果您的异常总是被抛出,您将收到编译错误,但这样的事情是完全合法的:

class Foo {

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

public Foo() throws Exception {

}

}

希望这能澄清一些事情。

答案 3 :(得分:1)

{ throw new Rex(); }

这意味着实例永远不会正确初始化。应该有一些条件可以正确初始化实例。 e.g。

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

如果抛出的异常是一个checked-exception,那么你必须将它添加到构造函数的throws子句中。 e.g。

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

    public MyObject() throws Exception {

    }
}

答案 4 :(得分:1)

来自http://www.artima.com/designtechniques/initializationP.html

  

实例初始值设定项中的代码可能无法返回。除了在   在匿名内部类的情况下,实例初始化器可能会抛出   仅在明确检查已检查的异常时才检查异常   在类中每个构造函数的throws子句中声明。   另一方面,匿名内部类中的实例初始值设定项   可以抛出任何异常。

答案 5 :(得分:1)

这由Java语言规范(Java SE 7)的section 8.6涵盖。

  

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

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

  

非空块中的每个其他语句S都不是交换机   如果S之前的语句可以完成,则可以访问该块   通常

  

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

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

那么为什么我们要拒绝具有(绝对)无法访问的语句的程序?因为它们几乎肯定代表了错误(在完成的代码中)。 (if语句特别适用于支持狡猾的条件编译。)

没有任何无法访问的语句,那么为什么实例初始化者必须能够正常完成(不需要构造函数以支持不可实例化的类)?因为这需要非本地分析,而Java不会这样做以保持相当简单,并且在维护期间可能会删除声明或仅重新排列代码的顺序。

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

答案 6 :(得分:0)

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