首页 > 解决方案 > 带有异常抛出构造函数的初始化块

问题描述

当某些方法或构造函数声明异常时,调用者必须处理它。当然,它甚至在初始化块中也有效。例如:

class Foo {
    private static Foo foo = new Foo();

    public Foo() throws Exception {
    }
}

第 2 行的编译错误,可以这样修复:

class Foo {
    static Foo foo;

    static {
        try {
            foo = new Foo();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Foo() throws Exception {
    }
}

但是当我们删除静态修饰符时,编译器不再发誓:

class Foo {
    public Foo foo = new Foo();

    public Foo() throws Exception {
    }
}

我知道,由于无限对象的创建,这段代码总是会被抛出StackOverflowException,但为什么没有关于声明异常的编译器错误?

标签: javaexceptionconstructor

解决方案


这个:

public Foo foo = new Foo();

public Foo() throws Exception {
  // Ctor body.
}

由编译器重新排列为(如果您不相信我,请尝试反编译该类的两种形式!):

public Foo foo;

public Foo() throws Exception {
  super();
  foo = new Foo();
  // Ctor body.
}

throws Exception所以它在处理异常的地方执行。


这在JLS 11.2.3中有描述(尽管没有解释):

如果命名类的实例变量初始化程序(第 8.3.2 节)或实例初始化程序(第 8.6 节)可以抛出已检查异常类,则这是编译时错误,除非命名类具有至少一个显式声明的构造函数和异常类或其超类之一在每个构造函数的 throws 子句中显式声明。

可以在JLS 12.5, Creation of New Class Instances中找到解释(项目符号 3、4 和 5 解释了我上面提到的重新排列;强调添加)

就在对新创建对象的引用作为结果返回之前,使用以下过程处理指示的构造函数以初始化新对象:

  1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。

  2. 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(第 8.8.7.1 节)开始(使用 this),则评估参数并使用这五个相同的步骤递归地处理该构造函数调用。如果该构造函数调用突然完成,则此过程出于相同原因而突然完成;否则,继续执行步骤 5。

  3. 此构造函数不以显式构造函数调用同一类中的另一个构造函数开始(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类构造函数(使用 super)开始。使用这五个相同的步骤递归地评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则此过程出于相同的原因突然完成。否则,继续执行步骤 4。

  4. 执行此类的实例初始化程序和实例变量初始化程序,将实例变量初始化程序的值分配给相应的实例变量,按照它们在类的源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个导致异常,则不会处理更多初始化程序,并且此过程会突然完成相同的异常。否则,继续执行步骤 5。

  5. 执行此构造函数的其余部分。如果该执行突然完成,则此过程出于同样的原因突然完成。否则,此过程正常完成。


推荐阅读