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

发布于 2024-08-17 22:35:07 字数 51 浏览 1 评论 0原文

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

Why doesn't Java allow to throw a checked exception from a static initialization block? What was the reason behind this design decision?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

冷弦 2024-08-24 22:35:07

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

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

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

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

Because it is not possible to handle these checked exceptions in your source. You do not have any control over the initialization process and static{} blocks cannot be called from your source so that you could surround them with try-catch.

Because you cannot handle any error indicated by a checked exception, it was decided to disallow throwing of checked exceptions static blocks.

The static block must not throw checked exceptions but still allows unchecked/runtime-exceptions to be thrown. But according to above reasons you would be unable to handle these either.

To summarize, this restriction prevents (or at least makes it harder for) the developer from building something which can result in errors from which the application would be unable to recover.

浅唱々樱花落 2024-08-24 22:35:07

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

You can work around the problem by catching any checked exception and rethrowing it as an unchecked exception. This unchecked exception class works well as a wrapper: java.lang.ExceptionInInitializerError.

Sample code:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

    static {
        try {
            _staticInstance = new YieldCurveConfigHelper();
        }
        catch (IOException | SAXException | JAXBException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}
我很OK 2024-08-24 22:35:07

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

It would have to look like this (this is not valid Java code)

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

but how would ad where you catch it? Checked exceptions require catching. Imagine some examples that may initialize the class (or may not because it is already initialized), and just to draw the attention of the complexity of that it would introduce, I put the examples in another static initalizer:

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?
  }
}

And another nasty thing -

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

Imagine ClassA had a static initializer throwing a checked exception: In this case MyInterface (which is an interface with a 'hidden' static initializer) would have to throw the exception or handle it - exception handling at an interface? Better leave it as it is.

清晰传感 2024-08-24 22:35:07

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

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

实际的 Java 限制是不允许检查的异常传播到块之外。

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

注意: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 来捕获 ExceptionInInitializerError2

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

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

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


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

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

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


如果您的代码“需要”在静态初始化程序中抛出异常,您应该怎么做?

基本上,有两种选择:

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

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

Why doesn't Java allow to throw a checked exception from a static initialization block?

Technically, you can do this. However, the checked exception must be caught within the block.

The actual Java restriction is that a checked exception is not allowed to propagate out of the block.

Technically, it is also possible to allow an unchecked exception to propagate out of a static initializer block1. But it is a really bad idea to do this deliberately! The problem is that the JVM itself catches the unchecked exception, and wraps it and rethrows it as a ExceptionInInitializerError.

NB: that ExceptionInInitializerError is an Error not a regular exception. You should not attempt to recover from it.

In most cases, the exception cannot be caught:

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)

There is nowhere you can place a try ... catch in the above to catch the ExceptionInInitializerError2.

In some cases you can catch it. For example, if you triggered the class initialization by calling Class.forName(...), you can enclose the call in a try and catch either the ExceptionInInitializerError or a subsequent NoClassDefFoundError.

However, if you attempt to recover from an ExceptionInInitializerError you are liable to run into a roadblock. The problem is that before throwing the error, the JVM marks the class that caused the problem as "failed". You simply won't be able to use it. Furthermore, any other classes that depend on the failed class will also go into failed state if they attempt to initialize. The only way forward is to unload all of the failed classes. That might be feasible for dynamically loaded code3, but in general it isn't.

1 - It is a compilation error if a static block unconditionally throws an unchecked exception.
2 - You might be able to intercept it by registering a default uncaught exception handler, but that won't allow you to recover, because your "main" thread can't start.
3 - If you wanted to recover the failed classes, you would need to get rid of the classloader that loaded them.


What was the reason behind this design decision?

It is to protect the programmer from writing code that throws exceptions that cannot be handled ... because there is no way for the programmer to write a handler.

As we have seen, an exception in a static initializer turns a typical application into a brick. The best thing that the language designers can do help the the programmer is to specified that the checked case 1s a compilation error. Unfortunately, it is not practical to do this for unchecked exceptions as well.


What should you do if your code "needs" to throw exceptions in a static initializer"?

Basically, there are two alternatives:

  • If (full!) recovery from the exception within the block is possible, then do that.

  • Otherwise, restructure your code so that the initialization doesn't happen in a static initialization block (or in the initializers of static variables). Put the initialization in a method or constructor that can be called from a regular thread.

三五鸿雁 2024-08-24 22:35:07

查看 Java 语言规范 :据说,如果静态初始化器失败能够突然完成并带有检查异常,则这是一个编译时错误。

Take a look at the Java Language Specifications: it is stated that it is a compile time error if static initializer fails is able to complete abruptly with a checked exception.

脱离于你 2024-08-24 22:35:07

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

Since no code you write can call static initialization block, it is not useful to throw checked exceptions. If it were possible, what would the jvm do when a checked exceptions are thrown? Runtimeexceptions are propagated up.

离旧人 2024-08-24 22:35:07

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

For example: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) handles the scenario which catches a checked exception and throws another unchecked exception.

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());
    }
诺曦 2024-08-24 22:35:07

我能够编译并抛出一个已检查的异常......

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

I am able to compile throwing a checked Exception Also....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文