Java:为什么抛出异常时会选择抛出Exception而不是特定的异常?

4

您想这样做的原因是什么:

void foo() throws Exception
{
    // Do something potentially exceptional
}

与其抛出一个现有的或自定义的异常?


1
测试方法通常包括该声明。 - Joey
10个回答

5

我能够想到两种可能的情况 - 第一种类似的情况是在实现 finalize() 时,你必须抛出 Throwable

@Override
protected void finalize() throws Throwable {
    super.finalize();
}

需要注意的是,有些人认为finalize方法本身应该被弃用。

可能出现的第二种情况是使用(编写不良的)库时,其方法可能会抛出异常。在这种情况下,如果您不想在该特定方法中处理它,则唯一的选择是将其传递到堆栈上。

个人而言,如果我遇到这种情况,我很可能会立即将其包装在RuntimeException中:

public void doSomething() {
    try {
        int x = libraryThing.badMethod(); //Library method that throws "Exception"
    }
    catch(Exception ex) {
        throw new RuntimeException("Couldn't do something", ex);
    }
}
RuntimeException的构造函数中的第二个参数在这种情况下非常重要,因为如果它被抛出,它将保留原始异常的堆栈跟踪信息如("Caused by: x")。当然,如果您可以找到一个更具体的RuntimeException子类,可以保证在该上下文中相关(例如IllegalArgumentException),那么使用它会更好。
但就正常代码而言,我认为这几乎总是一种反模式(通常只是由于懒惰引起!)
顺便说一下,抛出RuntimeException不算太糟糕 - 它仍然很不具体,但至少不会强制调用者明确地捕获所有异常。

将所有内容都包装成RuntimeException可能会使调试和处理异常变得更加困难。此外,它还会掩盖一个方法可能失败的事实,因为调用者不再具有明确的指示可以抛出异常。 - Durandal
@Durandal 这非常取决于上下文 - 当然,对所有异常都采用这种方式是非常糟糕的,有时候异常确实应该向上传播。这样做的重点在于当您不想在其他地方处理异常时(至于调试,这正是将原始异常作为运行时异常的因果参数传递的原因)。话虽如此,我发现最好的解决方案是尽量不要使用那些首先执行此类操作的库。 - Michael Berry

3

我不会这么做。它提供的信息最少,无法清楚地说明发生了什么。

我认为目前的最佳实践是优先使用未检查的异常(这是C#的方式)。foo()方法将捕获已检查的异常并将其包装在RuntimeException中。

我要么拼写出异常,要么将它们包装在更具业务特定性的自定义异常中,或者将RuntimeException包装起来。


3

它允许该方法抛出任意异常。

这可能在框架上下文中发现,其中任意代码以已知签名的方法运行。无论在那种情况下它是否“好”...嗯。我更愿意看到一个特定于框架或运行时的异常。

除此之外,在我看来,它通常是一种反模式。


3

我经常在我的测试方法中这样做。

@Test
public void testSOmething() throws Exception {

这是我用于单元测试的标准签名,不特别测试是否会抛出异常(大多数情况下都不会)。

在那些测试之外,我不关心我的测试可能会引发什么异常,因为在这些情况下,抛出异常代表被测试方法的失败。

不过,在生产代码中我从来不这么做。


1
你不写测试来展示异常可能会被抛出的情况(例如,如果缺少必需元素会发生什么)吗?我会这样做。在这种情况下,我使用@Expected注释。 - duffymo
我在我的单元测试方法中也是这样做的。在测试中,代码的清晰度很重要,嵌套的try/catch块可能会妨碍这一点。此外,捕获异常并抛出新的RuntimeException或记录它会使在IDE中跟踪问题的源变得更加困难,因为原始调用堆栈要么被遮蔽,要么根本不可用。 - Ed Griebel
@duffymo 我认为回答者所说的是当出现意外异常时,例如在数据库或IO操作期间,这在测试情况下基本上是无法恢复的。 - Ed Griebel
感谢澄清,Ed。我从未嵌套try/catch:一次只处理一个异常。听起来我们对如何处理这个问题达成了一致。 - duffymo
如果你正在对声明可以抛出已检查异常的方法进行单元测试,那么你必须要么尝试捕获语句(这对于测试来说不是一个好主意,因为可读性至关重要),要么声明你的测试方法抛出该异常。无论在运行时是否实际抛出异常都是无关紧要的。 - Mark Peters
@duffymo 澄清一下,我确实使用@Expected,我指的是不需要抛出异常的情况。 - Eric Wilson

2

如果实际列表非常长且不重要,则可以声明 throws Exception。例如,通过反射调用方法可能会导致多个异常。


2
你可能正在实现Java自己的Callable接口! 如果你提供了一些超结构以便其他人的代码可以运行,但你不想强制他们捕获所有受检异常。可以断言这不是你自己库的坏设计,而是受检异常本身的坏设计(但这样我们就会陷入宗教战争,而不是回答问题)。

1

一般来说,这意味着代码设计不良或底层库设计不良。如果你发现自己没有充分的理由就声明“throws Exception”,那么考虑抛出RuntimeException。特别是在库代码中。


或者(更好的是在库代码中)尝试找到一个更具体的RuntimeException来抛出 - 例如IllegalArgumentException或InvalidStateException。 - Michael Berry

0
通常情况下,我认为在由他人实现并且可能具有非常不同的实现方式的接口中声明“throws Exception”是可以接受的(您不希望限制它们可能抛出的异常)。
当一个方法需要抛出很多异常时,我也认为这是可以接受的。
我不认为这是一种反模式,而是一种经常因懒惰而使用的习惯用法。

0

通常在测试/快速粗略代码中完成,这是非常合理的。

throws列表很长和繁琐时,有时会这样做--半合理

一些开发人员/项目对所有内容都这样做,因为他们对“已检查”异常有不同的哲学,这是可以的(我自己对这个话题有两种想法),如果他们不打算与“世界其他地方”共享重要的代码。


0

根据业务规则定义自己的异常

public void doSomething() {
    try {
        int x = 10/0; //Library method that throws "Exception"
    }
    catch(Exception ex) {
        throw new Exception("this doesn;t work.there is exception", ex);
    }
}

这将覆盖异常方法;

class Exception
{
   Exception()
   {
   }

   Exception(String msg)
   {
      this.msg=msg;      
   }
}

只有你应该将其命名为“MyGenericException”或类似的名称。 - Hot Licks

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