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


135

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


您想在静态块中的哪种情况下抛出哪种异常?
Kai Huppmann 2010年

1
我不想做这样的事情。我只想知道为什么必须在静态块内捕获已检查的异常。
missingfaktor

您希望如何处理一个检查过的异常?如果让您感到困扰,只需抛出新的RuntimeException(“ Telling message”,e)来抛出捕获的异常。
托尔比约恩Ravn的安徒生

18
:@ThorbjørnRavnAndersen的Java实际上提供了这种情况的异常类型docs.oracle.com/javase/6/docs/api/java/lang/...
smp7d

@ smp7d请参阅下面的kevinarpe答案及其来自StephenC的评论。这是一个非常酷的功能,但是有陷阱!

Answers:


122

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

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

静态块不得引发已检查的异常,但仍允许引发未检查的/运行时异常。但是根据上述原因,您也将无法处理这些问题。

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


69
实际上,这个答案是不准确的。您可以在静态块中引发异常。您不能做的是允许检查的异常传播到静态块之外。
斯蒂芬·C 2010年

16
如果您正在使用Class.forName(...,true,...);进行动态类加载,则可以处理此异常。当然,这不是您经常遇到的事情。
LadyCailin

2
静态{抛出新的NullPointerExcpetion()}-这也将无法编译!
Kirill Bazarov 2014年

4
@KirillBazarov带有静态初始化程序的类始终会导致异常,因此无法编译(因为为什么要这么做?)。将抛出语句包装在if子句中,您就可以开始了。
2014年

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

67

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

1
@DK:也许您的Java版本不支持这种catch子句。试试:catch (Exception e) {代替。
kevinarpe

4
是的,您可以执行此操作,但这不是一个好主意。未检查的异常会将类和依赖于该类的任何其他类置于失败状态,该状态只能通过卸载这些类来解决。这通常是不可能的,和System.exit(...)(或同等学历)是唯一的选择,
斯蒂芬ç

1
@StephenC我们是否可以认为,如果“父”类无法加载,由于您的代码无法工作,实际上是否有必要加载其依赖类?您能否提供一些例子,说明无论如何都需要加载这样的依赖类?谢谢
Benj

怎么样……如果代码试图动态加载它;例如通过Class.forName。
斯蒂芬·C

21

它必须看起来像这样(这不是有效的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(这是一个带有“隐藏”静态初始化程序的接口)将不得不抛出该异常或对其进行处理-接口处的异常处理?最好保持原样。


7
main可以抛出检查异常。显然那些无法处理。
机械蜗牛

@Mechanicalsnail:有趣的一点。在我的Java心理模型中,我假设在运行的线程上附加了一个“魔术”(默认)Thread.UncaughtExceptionHandler main(),该异常将带有堆栈跟踪的异常打印到System.err,然后调用System.exit()。最后,这个问题的答案可能是:“因为Java设计人员是这样说的”。
kevinarpe17年

7

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

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

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

注意:这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在上面放置a 来捕获ExceptionInInitializerError2

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

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

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

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

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


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

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

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


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

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

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


关于如何构造代码,以便不进行任何静态初始化,是否有任何一般性建议?
MasterJoe2


1
1)我没有。2)听起来很糟糕。看到我留下的评论。但是,我只重复上面我在“答案”中所说的内容。如果您阅读并理解了我的答案,您将知道这些“解决方案”不是解决方案。
斯蒂芬C,

4

看一下Java语言规范:它指出,如果静态初始化程序失败 并能够通过检查的异常突然完成,则这是编译时错误。


5
但是,这并不能回答问题。他问为什么这是一个编译时错误。
温斯顿·史密斯

嗯,所以抛出任何RuntimeError应该是可能的,因为JLS仅提及已检查的异常。
安德烈亚斯·多克

没错,但是您永远不会将其视为堆栈跟踪。因此,在使用静态初始化块时需要格外小心。
EJB 2010年

2
@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.<clinit>(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
KonradHöffner2012年

部分表示“所致”堆栈跟踪你可能更感兴趣的
LadyCailin

2

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


1
好吧,是的,我现在明白了。我这样发布问题真是愚蠢。但是a ...我现在不能删除它。:(尽管如此,您的回复还是+1 ...
missingfaktor 2010年

1
@fast,实际上,检查的异常不会转换为RuntimeExceptions。如果您自己编写字节码,则可以将静态初始化程序中的检查异常抛出您的内心。JVM根本不关心异常检查。它纯粹是一种Java语言构造。
锑2012年

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

1
这样就解决了无法捕获未经检查的异常的问题。相反,它将类和依赖它的任何其他类置于不可恢复的状态。
Stephen C

@StephenC-您能否举一个简单的例子,说明我们想要一个可恢复的状态?
MasterJoe2 '19

假设...如果您希望能够从IOException中恢复,以便应用程序可以继续运行。如果要这样做,则必须捕获异常并进行实际处理……不要抛出未经检查的异常。
斯蒂芬·C

-5

我也可以通过编译来检查异常。

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

3
是的,但是您正在静态块中捕获它。您不允许将检查后的异常从静态块内部抛出到外部。
ArtOfWarfare
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.