【问题标题】:Why is it not allowed to throw an exception in a Java instance initialization block?为什么不允许在 Java 实例初始化块中抛出异常?
【发布时间】:2012-11-23 16:30:11
【问题描述】:

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

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

【问题讨论】:

  • @AlexWien 你知道类和对象的区别吗? ;-)
  • 是的,但不存在实例初始化器,我只知道静态初始化器。

标签: java


【解决方案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);
    }
}

编译,运行时抛出

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

【讨论】:

  • 编译器没有对代码进行足够深入的分析,看不出它总是会抛出。在您的示例中,您将 {{ throw something; }} 放在静态初始化程序中,这很明显。尝试一些永远正确但不明显的事情,{{ if (Math.sin(3) &lt; 0.5) { throw new ArithmeticException; } else { whatever; } }}
  • 谢谢! if (true) throw ... 就够了。
  • 我不确定它是否会,所以我拿了一些我确信编译器不会看穿的东西。
  • 异常检查确​​实是 Java 最半途而废(或者我应该说是愚蠢)的想法之一。
  • 我不会说这是一个愚蠢的想法,结果证明它并不是那么好。当时,说方法必须记录它们抛出的异常类型以便调用者知道需要处理什么[除了预期不可恢复的运行时异常]并不是一个愚蠢的想法。它只是没有像希望的那样工作,但后来改变为时已晚(或者它仍然可能吗?)。
【解决方案2】:

Java 被设计为具有最少的功能,并且只有在有充分理由这样做时才会增加复杂性。 Java 不问;为什么不呢,它问;我真的需要支持吗? (即使这样有时也不会;)

初始化程序块的代码必须插入到每个构造函数中,编译器知道在编译器难以为其生成代码的情况下无法正常完成的块。

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


在这种特定情况下它对您没有帮助,但知道这一点很有用.....

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

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

【讨论】:

  • 以及如何创建未经检查的异常?
  • 未经检查的异常扩展RuntimeException。没有很好的方法来处理一个本来会被检查的异常,比如IOException,然后让它不被检查。但是,如果您创建一个直接或间接扩展 RuntimeException 的新类型,那么您可以抛出它的一个实例,而无需添加 throws 子句。
【解决方案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
    

    如果抛出的异常是已检查异常,则必须将其添加到构造函数的 throws 子句中。例如

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

    【讨论】:

      【解决方案5】:

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

      实例初始化器中的代码可能不会返回。除了在 在匿名内部类的情况下,实例初始化程序可能会抛出 仅当显式检查异常时才检查异常 在类中每个构造函数的 throws 子句中声明。 另一方面,匿名内部类中的实例初始化器, 可以抛出任何异常。

      【讨论】:

      • 另一方面,匿名内部类中的实例初始化器可以抛出任何异常。这是一个强大的小功能,感谢您找到这个细节
      【解决方案6】:

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

      如果实例初始化程序无法完成,这是一个编译时错误 通常 (§14.21)。

      14.21 定义了不可达的含义。特别注意

      非空块中的所有其他语句 S 不是 switch 如果 S 之前的语句可以完成,则块是可到达的 正常。

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

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

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

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

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          猜你喜欢
          • 2011-01-05
          • 1970-01-01
          • 1970-01-01
          • 2013-07-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-17
          相关资源
          最近更新 更多