关于java:让特定异常绕过广泛的catch块的成语?

Idiom to let a specific exception bypass a broad catch block?

通常,在实现模板方法或接口方法时,只能抛出由该方法定义的一种特定类型的异常。但是,您的实现可能会使类指向抛出不兼容异常类型或许多不同异常类型的API。

当然,您需要捕获它们并将异常包装到适合于实现的方法签名的类型中。假设我们想要实现这个接口:

1
2
3
4
5
public interface SomeDataGetter {

    public long getSomeData() throws IOException;

}

我们的实现使用其他一些API产品来实现这一点,我们调用的API方法可能具有以下签名:

1
public long loadFromDBOrCache(Object ... params) throws SQLException, IOException, ObjectNotFoundException, RuntimeException, FridayException, NotWeekendException, NumberIs42Exception;

我这样做是为了演示这样一种情况,即您不能精确地枚举所有可能由具体类型引发的异常。请注意,IOException是允许我们从实现中抛出的类型。

现在,我可以在实现这一点时采用懒惰的方式,并包装任何适合我签名的内容:

1
2
3
4
5
6
7
8
@Override
public long getSomeData() throws IOException {
    try {
        return loadFromDB(...);
    } catch (Exception e) {
        throw new IOException(e.getMessage(), e);
    }
}

很明显,这会将任何异常包装成一个IOException(甚至是IOException),结果正常。但我不想包装IOExceptions,因为允许我不包装就扔:

1
2
3
4
5
6
7
8
9
10
@Override
public long getSomeData() throws IOException {
    try {
        return loadFromDB(...);
    } catch (IOException e) {
        throw e;
    } catch (Exception e) {
        throw new IOException(e.getMessage(), e);
    }
}

如果实现中存在多个可能的异常,并且允许实现中存在多个异常,您可以想象这会很快变得很麻烦。我需要为每一个我想通过的例外多抓一把。

什么是最好的成语来保持可读性(同样,我很懒惰,不想写所有这些额外的捕获),并且仍然避免不必要的异常嵌套?或者我应该不费心把所有东西都包起来吗?


一种方法是创建一个方法,将所有"禁止"的异常都包装在一个允许的异常中,同时返回所有允许的未包装的异常,如下所示:

1
2
3
4
5
6
7
8
9
private static void throwIoException(Exception e)
    throws IOException // <<= Add other"allowed" exceptions here
{
    if (e instanceof IOException) {
        throw (IOException)e;
    }
    ... // <<= Add checks for other"allowed" exceptions here
    throw new IOException(e.getMessage(), e);
}

现在您可以使用单个catch块,并根据需要进行包装:

1
2
3
4
5
try {
    return loadFromDB(...);
} catch (Exception e) {
    throwIoException(e);
}

这样做的一个不愉快的结果是,堆栈跟踪在新创建的IOException的顶部显示实用方法,但这并不重要,因为真正的异常是被包装的,而不是IOException包装。如果捕获的异常恰好是IOException,则正确的堆栈跟踪应该保持在适当的位置。


我会认为把你进入IOExceptions(或另一个选中的异常)的所有异常打包起来的懒惰做法是一种糟糕的做法。相反,我会考虑将异常包装在运行时异常中,从而绕过catch或指定需求。例如。

1
2
3
4
5
6
7
8
@Override
public long getSomeData() throws IOException {
    try {
        return loadFromDB(...);
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

之所以这样做更好,是因为选中的异常具有一定的含义。如果您在代码中捕捉到一个ParseException,然后将其作为IOException重新发送,那么您是在撒谎。作为代码的用户,我可能可以对某些类型的已检查异常做些什么,但是如果您混淆了异常的真正原因,那么在发生错误时调试代码将更加困难。

一般来说,我认为您应该尽量减少使用选中的异常,因为它会在整个应用程序中丢弃错误处理代码。另外,如果您使用的是其他人的代码,则无法保证不会抛出RuntimeException(除非您仔细阅读了所有代码)。因此,无论如何,您必须考虑这种可能性,并在某个地方处理它,这样您的应用程序就不会崩溃。例如,未检查异常和检查异常的优点在其他地方和这里已经讨论了很多。