【问题标题】:Why doesn't Java allow to throw a checked exception from static initialization block?为什么 Java 不允许从静态初始化块中抛出已检查的异常?
【发布时间】:2011-01-05 10:21:42
【问题描述】:

为什么 Java 不允许从静态初始化块中抛出已检查异常?这个设计决定背后的原因是什么?

【问题讨论】:

  • 在静态块中,你想在什么样的情况下抛出什么样的异常?
  • 我不想做那样的事情。我只是想知道为什么必须在静态块中捕获已检查的异常。
  • 那么您希望如何处理已检查的异常?如果它困扰您,只需使用 throw new RuntimeException("Telling message", e); 重新抛出捕获的异常;
  • @ThorbjørnRavnAndersen Java 实际上为这种情况提供了一个异常类型:docs.oracle.com/javase/6/docs/api/java/lang/…
  • @smp7d 请参阅下面的 kevinarpe 答案以及 StephenC 的评论。这是一个非常酷的功能,但它有陷阱!

标签: java exception static-initializer


【解决方案1】:

因为无法在您的源代码中处理这些已检查的异常。您无法控制初始化过程,并且无法从您的源代码调用静态块,因此您可以用 try-catch 将它们包围。

由于您无法处理已检查异常指示的任何错误,因此决定禁止抛出已检查异常静态块。

静态块不能抛出 checked 异常,但仍允许抛出 unchecked/runtime-exceptions。但是根据上述原因,您也无法处理这些。

总而言之,此限制可防止(或至少使其更难)开发人员构建可能导致应用程序无法从中恢复的错误的东西。

【讨论】:

  • 其实这个答案是不准确的。您可以在静态块中引发异常。你不能做的是允许 checked 异常传播出一个静态块。
  • 如果您自己进行动态类加载,您可以使用 Class.forName(..., true, ...); 处理此异常;当然,这不是你经常遇到的事情。
  • static { throw new NullPointerExcpetion() } - 这也不会编译!
  • @KirillBazarov 具有静态初始化程序的类总是导致异常将无法编译(因为为什么要编译?)。将该 throw 语句包装在 if 子句中,就可以开始了。
  • @Ravisha 因为在这种情况下,初始化程序在任何情况下都没有机会正常完成。使用 try-catch 可能不会抛出 println 异常,因此初始化程序有机会无异常地完成。这是异常的无条件结果,使其成为编译错误。请参阅 JLS:docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 但编译器可能仍会通过在您的情况下添加一个简单条件来愚弄:static { if(1 < 10) { throw new NullPointerException(); } }
【解决方案2】:

您可以通过捕获任何已检查的异常并将其作为未检查的异常重新抛出来解决此问题。这个未经检查的异常类可以很好地用作包装器:java.lang.ExceptionInInitializerError

示例代码:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

    static {
        try {
            _staticInstance = new YieldCurveConfigHelper();
        }
        catch (IOException | SAXException | JAXBException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}

【讨论】:

  • @DK:也许您的 Java 版本不支持这种类型的 catch 子句。试试:catch (Exception e) {
  • 是的,你可以这样做,但这是一个非常糟糕的主意。未经检查的异常将类和依赖它的任何其他类置于失败状态,只能通过卸载类来解决。这通常是不可能的,System.exit(...)(或同等学历)是您唯一的选择,
  • @StephenC 我们是否可以认为如果“父”类无法加载,实际上没有必要加载它的依赖类,因为您的代码将无法工作?您能否提供一些示例,说明无论如何都需要加载这样的依赖类?谢谢
  • 如果代码尝试动态加载它怎么样?例如通过 Class.forName。
【解决方案3】:

它必须看起来像这样(这是不是有效的 Java 代码)

// Not a valid Java Code
static throws SomeCheckedException {
  throw new SomeCheckedException();
}

但是如何在你捕捉到它的地方做广告呢?已检查的异常需要捕获。想象一些可能初始化类的示例(或者可能不会,因为它已经初始化了),为了引起人们注意它会引入的复杂性,我将示例放在另一个静态初始化器中:

static {
  try {
     ClassA a = new ClassA();
     Class<ClassB> clazz = Class.forName(ClassB.class);
     String something = ClassC.SOME_STATIC_FIELD;
  } catch (Exception oops) {
     // anybody knows which type might occur?
  }
}

还有一件令人讨厌的事情-

interface MyInterface {
  final static ClassA a = new ClassA();
}

想象 ClassA 有一个静态初始化器抛出一个检查异常:在这种情况下,MyInterface(它是一个带有“隐藏”静态初始化器的接口)必须抛出异常或处理它 - 接口处的异常处理?最好保持原样。

【讨论】:

  • main 可以抛出检查异常。显然这些是无法处理的。
  • @Mechanicalsnail:有趣的一点。在我的 Java 心智模型中,我假设有一个“神奇的”(默认)Thread.UncaughtExceptionHandler 附加到运行main() 的线程,它将带有堆栈跟踪的异常打印到System.err,然后调用System.exit()。最后,这个问题的答案大概是:“因为Java设计者这么说”。
【解决方案4】:

为什么 Java 不允许从静态初始化块中抛出已检查异常?

从技术上讲,您可以做到这一点。但是,检查的异常必须在块内捕获。

实际的 Java 限制是不允许已检查的异常传播出块。

从技术上讲,还可以允许未检查异常从静态初始化块1传播出去。但是故意这样做是一个非常糟糕的主意!问题是 JVM 本身捕获了未经检查的异常,并将其包装并重新抛出为 ExceptionInInitializerError

注意:ExceptionInInitializerErrorError 不是常规异常。您不应该尝试从中恢复。

在大多数情况下,无法捕获异常:

public class Test {
    static {
        int i = 1;
        if (i == 1) {
            throw new RuntimeException("Bang!");
        }
    }
    
    public static void main(String[] args) {
        try {
            // stuff
        } catch (Throwable ex) {
            // This won't be executed.
            System.out.println("Caught " + ex);
        }
    }
}

$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
    at Test.<clinit>(Test.java:5)

您无法在上面放置try ... catch 来捕捉ExceptionInInitializerError2

在某些情况下,您可以抓住它。例如,如果您通过调用 Class.forName(...) 触发了类初始化,则可以将调用包含在 try 中并捕获 ExceptionInInitializerError 或后续的 NoClassDefFoundError

但是,如果您尝试从ExceptionInInitializerError恢复,您可能会遇到障碍。问题是在抛出错误之前,JVM 将导致问题的类标记为“失败”。你根本无法使用它。此外,任何其他依赖于失败类的类在尝试初始化时也将进入失败状态。唯一的出路是卸载所有失败的类。这可能对于动态加载的代码是可行的3,但一般情况下并非如此。

1 - 如果一个静态块无条件抛出一个未经检查的异常,这是一个编译错误。
2 - 您可能可以通过注册一个默认的未捕获异常处理程序来拦截它,但这不会让您恢复,因为您的“主”线程无法启动。
3 - 如果你想恢复失败的类,你需要摆脱加载它们的类加载器。


这个设计决定背后的原因是什么?

是为了保护程序员不写出抛出无法处理的异常的代码……因为程序员没有办法写handler。

正如我们所见,静态初始化程序中的异常会将典型应用程序变成砖块。语言设计人员可以帮助程序员的最好的事情是指定检查的案例 1 是编译错误。不幸的是,对未经检查的异常也这样做是不切实际的。


好的,那么如果您的代码“需要”在静态初始化程序中抛出异常,您应该怎么做。基本上,有两种选择:

  • 如果(完全!)从块内的异常中恢复是可能的,那么就这样做。

  • 否则,请重新构造您的代码,以便初始化不会发生在静态初始化块(或静态变量的初始化程序)中。将其放在可以从常规线程调用的方法或构造函数中。

【讨论】:

  • 是否有关于如何构造代码以使其不进行任何静态初始化的一般建议?
  • 1) 我没有。 2)它们听起来很糟糕。看看我留在他们身上的 cmets。但我只是重复我在上面的答案中所说的话。如果您阅读并理解我的回答,您就会知道那些“解决方案”不是解决方案。
【解决方案5】:

看看Java Language Specifications:如果静态初始化程序fails 能够突然完成并带有检查的异常,则说明这是一个编译时错误。

【讨论】:

  • 这并没有回答这个问题。他问为什么这是一个编译时错误。
  • 嗯,所以抛出任何 RuntimeError 应该是可能的,因为 JLS 只提到了检查的异常。
  • 没错,但你永远不会看到堆栈跟踪。这就是为什么您需要小心使用静态初始化块。
  • @EJB:这是不正确的。我刚试了一下,下面的代码给了我一个可视的堆栈跟踪:public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} } 输出:Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: whathappenswhenastaticblockthrowsanexception at Main.&lt;clinit&gt;(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
  • “Caused by”部分显示了您可能更感兴趣的堆栈跟踪。
【解决方案6】:

由于您编写的代码都不能调用静态初始化块,因此抛出检查exceptions 没有用。如果可能,当抛出检查异常时 jvm 会做什么? Runtimeexceptions 向上传播。

【讨论】:

  • 嗯,是的,我现在明白了。发表这样的问题真是太愚蠢了。但是唉...我现在不能删除它。 :( 不过,为您的回复 +1...
  • @fast,实际上,检查的异常不会转换为 RuntimeExceptions。如果您自己编写字节码,则可以在静态初始化程序中将已检查的异常抛出到您心中的内容。 JVM 根本不关心异常检查;它纯粹是一种 Java 语言结构。
【解决方案7】:

例如:Spring 的 DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) 处理捕获已检查异常并抛出另一个未检查异常的场景。

static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }

【讨论】:

  • 这种方法解决了无法捕获未检查异常的问题。相反,它将类和任何其他依赖于它的类置于不可恢复的状态。
  • @StephenC - 你能否举一个简单的例子,我们希望有一个可恢复的状态?
  • 假设...如果您希望能够从 IOException 中恢复,以便应用程序可以继续。如果你想这样做,那么你必须捕获异常并实际处理它......不要抛出未经检查的异常。
【解决方案8】:

我也可以编译抛出一个检查的异常......

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}

【讨论】:

  • 是的,但您在静态块中捕获它。不允许将已检查的异常从静态块内部抛出到外部。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-13
  • 1970-01-01
  • 1970-01-01
  • 2017-12-07
  • 1970-01-01
相关资源
最近更新 更多