在Java中将受检异常包装为未受检异常?

34

我有这样一个Java中的工厂方法:

public static Properties getConfigFactory() throws ClassNotFoundException, IOException {
    if (config == null) {
        InputStream in = Class.forName(PACKAGE_NAME).getResourceAsStream(CONFIG_PROP);
        config = new Properties();
        config.load(in);
    }
    return config;
}

我想将这两个被检查的异常转换为未经检查的异常。最好的方法是什么?

我应该捕获异常并使用已捕获的异常作为内部异常抛出新的RuntimeException吗?

还有更好的方法吗?或者我是否应该首先尝试这样做?

编辑:
只是为了澄清。这些异常将是致命的,因为配置文件对程序的操作非常重要,并且所有异常都将在我的程序顶层被捕获和记录。

我的目的是避免不必要的throws异常,它会添加到调用我的工厂的每个方法的签名中。


你这么做的目的是什么?为什么不让方法调用者决定如何处理异常呢? - DMKing
2
我的工厂背后的想法是避免过多的代码。我宁愿不必每次使用它时都要有一个try catch块。 - James McMahon
你可以编写一个辅助函数将抛出的异常转换为未检查的异常。在你的代码中,你可以调用类似 callUnchecked(() -> getConfigFactory()) 的东西,并定义 callUnchecked 类似于 try { return supplier.get(); } catch (Throwable ex) { throw new RuntimeException(ex); }。这样,你在调用者代码中明确声明调用可能会失败并导致崩溃,但不会使代码过于臃肿。显然,这仅适用于 Java 8,因为涉及到 lambda。 - Qw3ry
4个回答

41
只有当客户端无法从问题中恢复时,才应使用RuntimeException。有时这样做是合适的,但更多情况下是不合适的。
如果您正在使用JDK> = 1.4,则可以执行以下操作:
尝试{   //可能会引发异常的代码 } catch(IOException e){   throw new RuntimeException(e); } catch(ClassNotFoundException e){   throw new RuntimeException(e); }
重新抛出的RuntimeException将包含其中的原始原因。这样,位于线程顶部捕获RuntimeException的人 - 您的线程是否捕获RuntimeException,以便它们不会静默死亡? - 至少可以打印出原因的完整堆栈跟踪。
但正如其他人所说,异常是有原因的。只有在确定客户端无法从重抛为未经检查的异常的问题中恢复时才这样做。
注意:如果有更具体的未经检查的异常可用,则最好使用它而不仅仅是RuntimeException。例如,如果您的方法可能抛出ClassNotFoundException的唯一原因是缺少配置文件,则可以重新抛出MissingResourceException,它是一个未经检查的异常,但提供了更多有关为什么抛出它的信息。如果它们描述您要重新抛出的问题,其他好的RuntimeException包括IllegalStateExceptionTypeNotPresentExceptionUnsupportedOperationException
还要注意,始终将捕获运行时异常并至少将其记录下来是一个好习惯。至少这样你知道为什么你的线程消失了。

4
如果可能的话,我更喜欢将我的异常保持为通用形式,避免使用自定义异常。 - James McMahon
4
避免使用自定义异常是可以的,这就是为什么我提到了作为Java一部分已经存在的几个最有用的RuntimeException。一个更具体的异常在调试期间会更有帮助。 - Eddie
1
然后你必须说:m = new MissingResourceException(); m.initCause(e); throw m; - Adrian Pronk
3
啊,Java。有什么你不能使得不必要的冗长吗? - James McMahon
@JamesMcMahon:我认为不幸的是,没有一种好的方式来表达“应该将在此代码块内调用的方法中抛出的任何FooException视为意外情况,并将其包装在BarException中”。即使代码块本身执行了throw new FooException(),在嵌套方法中抛出的FooException也可能意味着一个状态,这个状态与捕获FooException的调用代码的期望不匹配。 - supercat
显示剩余4条评论

15

关于异常处理最佳实践有两个要点:

  • 调用代码无法处理该异常 -> 将其设为未检查异常
  • 调用代码将根据异常中的信息采取一些有用的恢复动作 -> 将其设为已检查异常

如果你抛出RuntimeException,可以选择是否带有内部异常,这取决于调用者对其的处理方式。如果不会重新抛出内部异常,则应在方法中记录该异常日志(如果这很重要)。


4

您正在以正确的方式进行操作。

为了让已检查的异常通过而不被检查,您必须将它们包装在未检查的异常中。

请记住,异常是有原因被检查的。


检查异常是一种可恶的东西。它有一些缺点。首先,它传染给调用者代码(只需在 throws 中添加新异常并准备进行大规模重构)。其次,无论如何调用者都必须准备处理未检查的异常,Java 8 函数式 API 不喜欢检查异常。 - mhstnsc
@mhstnsc,“viral”是一种优势。这类似于更改返回类型。它应该会破坏调用者代码。调用者代码符合一个不再存在的合同。 - Vsevolod Golovanov
1
@VsevolodGolovanov 是的,但是这个合同完全被RuntimeExceptions无效化了。如果没有RuntimeExceptions,我同意你的观点。RuntimeExceptions是解决这个病毒性问题的补丁。它们可能会因为想要在实现中添加新的异常流而产生巨大的向后兼容性问题。如果你管理自己的代码,那很容易,但如果你将代码分成向后兼容的层,你很快就会像我一样讨厌它 :) - mhstnsc

1

如果你想避免太多的try - catch,在工厂本身捕获并处理这两个异常。 也许返回一个默认实现。

无论如何,你必须在这里或其他地方处理这些异常。

既然这是一个工厂,我认为最好在工厂本身处理这些异常(同一个方法或不同的方法),并返回一个默认实现。

无论如何,(业务功能)调用者在遇到ClassNotFoundException时将不知道该做什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接