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

时间:2010-01-15 08:31:14

标签: java exception static-initializer

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

8 个答案:

答案 0 :(得分:105)

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

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

静态块不能抛出已检查异常,但仍允许抛出未经检查/运行时异常。但根据上述原因,你也无法处理这些问题。

总而言之,这种限制可以防止(或者至少使开发人员更难以)构建可能导致应用程序无法恢复的错误的内容。

答案 1 :(得分:63)

您可以通过捕获任何已检查的异常并将其重新抛出为未经检查的异常来解决此问题。这个未经检查的异常类可以作为包装器使用: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);
        }
    }
}

答案 2 :(得分:19)

它必须看起来像这样(这是有效的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(这是一个带有'hidden'静态初始化器的接口)必须抛出异常或处理它 - 接口处的异常处理?最好保持原样。

答案 3 :(得分:7)

  

为什么Java不允许从静态初始化块引发已检查的异常?

从技术上讲,您可以执行此操作。但是,必须在块中捕获已检查的异常。不允许将已检查的异常传播到该块之外。

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

NB:这是一个Error,不是常规异常。您不应尝试从中恢复。

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

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的地方可以捕捉ExceptionInInitializerError 2

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

但是,如果您尝试从ExceptionInInitializerError 恢复,则可能会遇到障碍。问题在于,在引发错误之前,JVM将导致问题的类标记为“失败”。您将无法使用它。此外,依赖于失败类的任何其他类如果尝试初始化也将进入失败状态。前进的唯一方法是卸载所有失败的类。对于动态加载的代码 3 来说,可能是可行的,但总的来说不是。

1-如果静态块无条件抛出未经检查的异常,则是编译错误。

2-您可能可以通过注册默认的未捕获异常处理程序来拦截它,但这将使您无法恢复,因为您的“主”线程无法启动

3-如果要恢复失败的类,则需要摆脱加载它们的类加载器。


  

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

这是为了保护程序员避免编写引发 无法处理的异常的代码!

我们已经看到,静态初始化程序中的异常将典型的应用程序变成砖头。语言设计师可以做的最好的事情是将检查的情况视为编译错误。 (不幸的是,对于未经检查的异常也不能这样做。)


好的,所以如果您的代码“需要”在静态初始化程序中引发异常,该怎么办。基本上,有两种选择:

  1. 如果可以从块内的异常中恢复(完全!),请执行此操作。

  2. 否则,请重新组织代码,以使初始化不会在静态初始化块(或静态变量的初始化器)中发生。

答案 4 :(得分:4)

看看Java Language Specifications:如果静态初始值设定项失败 能够突然完成,则表示编译时错误异常。

答案 5 :(得分:2)

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

答案 6 :(得分:0)

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

答案 7 :(得分:-3)

我能够编译抛出一个已检查的Exception ...

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