在Java中,何时应该创建一个已检查的异常,何时应该是运行时异常?

In Java, when should I create a checked exception, and when should it be a runtime exception?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
When to choose checked and unchecked exceptions

我应该何时创建一个已检查的异常,何时应该生成运行时异常?

例如,假设我创建了以下类:

1
2
3
4
5
6
7
8
9
10
11
public class Account {
    private float balance;

    /* ... constructor, getter, and other fields and methods */

    public void transferTo(Account other, float amount) {
        if (amount > balance)
            throw new NotEnoughBalanceException();
        /* ... */
    }
}

我该如何创建NotEnoughBalanceException? 它应该延伸Exception还是RuntimeException? 或者我应该只使用IllegalArgumentException


关于这个话题存在很多分歧。在我上一份工作中,我们遇到了一些实际问题,运行时异常被遗忘,直到它们出现在生产中(在agedwards.com上),因此我们决定仅使用已检查的异常。

在我目前的工作中,我发现在很多或所有情况下都有很多人会遇到运行时异常。

这就是我的想法:使用CheckedExceptions,我在编译时被迫至少在调用者中确认异常。对于运行时异常,我不会被编译器强迫,但可以编写一个单元测试,让我处理它。因为我仍然认为越早发现一个bug就越便宜,我就更喜欢CheckedExceptions。

从哲学的角度来看,方法调用是调用者和被调用者之间某种程度的契约。由于编译器强制执行传入的参数类型,因此它似乎是对称的,以便在出路时强制执行类型。也就是说,返回值或异常。

我的经验告诉我,当我使用经过检查的异常时,我获得了更高的质量,也就是代码。检查异常可能会使代码混乱,但有一些技术可以解决这个问题。我喜欢在传递图层边界时翻译异常。例如,如果我从持久层中传出,我想将SQL异常转换为持久性异常,因为下一层不应该关心我是否持久化到SQL数据库,但是想要知道是否有东西不能坚持下去。我使用的另一种技术是创建一个简单的异常层次结构。这让我可以在一层编写更清晰的代码,因为我可以捕获超类,并且只在真正重要时处理各个子类。


总的来说,我认为Joshua Bloch在Effective Java中的建议最好地总结了您的问题的答案:使用已检查的可恢复条件和编程错误的运行时异常(第2版中的第58项)。

所以在这种情况下,如果你真的想要使用异常,它应该是一个经过检查的异常。 (除非transferTo()的文档清楚地表明,如果不首先使用其他Account方法检查足够的平衡,就不能调用该方法 - 但这看起来有点尴尬。)

但另请注意项目59:避免不必要地使用已检查的例外和57:仅在例外条件下使用例外。正如其他人所指出的那样,这个案件可能根本不需要例外。如果没有足够的信用,请考虑返回false(或者可能是状态对象,其中包含有关所发生情况的详细信息)。


何时使用已检查的例外?说实话?以我的拙见......从不。我认为自从我上次创建一个检查异常以来已经过去了大约6年。

你不能强迫某人处理错误。可以说,它使代码变得更糟,而不是更好。我不能告诉你我遇到这样的代码的次数:

1
2
3
4
5
try {
  ...
} catch (IOException e) {
  // do nothing
}

虽然我有无数次编写这样的代码:

1
2
3
4
5
try {
  ...
} catch (IOException e) {
  throw new RuntimeExceptione(e);
}

为什么?因为一个条件(不一定是IOException;这只是一个例子)是不可恢复的,但是无论如何都被迫放下我的喉咙而且我经常被迫在执行上述操作和污染我的API之间做出选择,只是为了传播一个检查过的异常到顶部它(正确地)致命并将被记录。

Spring的DAO帮助程序类将检查的SQLException转换为未经检查的DataAccessException是有原因的。

如果您对磁盘缺少写入权限,缺少磁盘空间或其他致命条件,您希望尽可能多地产生噪声,并且执行此操作的方法是使用...未经检查的异常(甚至是错误)。

此外,已检查的异常会破坏封装。

检查异常的这个想法应该被用于"可恢复的"错误,这实际上是一种天空般的一厢情愿的想法。

Java中检查的异常是一个实验......一个失败的实验。我们应该减少损失,承认我们犯了错误并继续前进。恕我直言.Net通过仅具有未经检查的异常使其正确。然后,它再次获得了从Java的错误中学习的第二个优势。


我最近遇到了异常问题,代码抛出了NullPointerException,我不知道为什么,经过一些调查后发现真正的异常被吞噬了(它是在新代码中,所以它仍在完成)并且方法只返回n??ull。如果你检查了异常,你必须明白坏程序员只会尝试捕获它并忽略异常。


恕我直言,它应该不是一个例外。在我看来,应该在特殊情况发生时使用异常,而不是作为流控制。

在你的情况下,有人试图转移比余额允许更多的钱,这并不是一个特殊的状态。我认为这些事情在现实世界中经常发生。所以你应该针对这些情况进行编程。一个例外可能是你的if语句评估余额是好的,但是当实际从账户中扣除钱时,由于一些奇怪的原因,余额不再好了。

例外情况可能是,在调用transferTo()之前,您检查了该行是否已向银行开放。但是在transferTo()中,代码注意到该行不再打开,但是,按照所有逻辑,它应该是。这是一个例外。如果线路无法打开,那也不例外,这是一个看似合理的情况。

恕我直言回顾:例外==奇怪的黑魔法。

是建设性编辑:

所以,不要太矛盾,方法本身可能会抛出异常。但是应该控制该方法的使用:首先检查余额(在transferTo()方法之外),如果余额良好,则只调用transferTo()。如果transferTo()注意到余额,由于某些奇怪的原因,不再是好的,你抛出了你努力捕获的异常。

在这种情况下,你有你的所有鸭子,并且知道你无能为力(因为什么是true变成false,就像它本身一样),除了记录异常,发送通知给有人,礼貌地告诉顾客,有人在上一次满月期间没有正确地牺牲他们的处女,问题将在第一个可能的时刻得到解决。

不太enterprisey暗示编辑:

如果你这样做是为了你自己的乐趣(并且情况似乎是这样,请参阅注释),我建议返回一个布尔值。用法是这样的:

1
2
3
4
5
6
7
8
9
10
// ...
boolean success = transferTo(otherAccount, ONE_MILLION_DOLLARS_EXCLAMATION);

if (!success) {
  UI.showMessage("Aww shucks. You're not that rich");
  return; // or something...
} else {
  profit();
}
// ...


从未经检查的例外 - 争议:

If a client can reasonably be expected
to recover from an exception, make it
a checked exception. If a client
cannot do anything to recover from the
exception, make it an unchecked
exception.

请注意,未经检查的异常是从RuntimeException派生的异常,而已检查的异常是从Exception派生的异常。

如果客户端无法执行任何操作以从异常中恢复,为什么抛出RuntimeException?文章解释说:

Runtime exceptions represent problems
that are the result of a programming
problem, and as such, the API client
code cannot reasonably be expected to
recover from them or to handle them in
any way. Such problems include
arithmetic exceptions, such as
dividing by zero; pointer exceptions,
such as trying to access an object
through a null reference; and indexing
exceptions, such as attempting to
access an array element through an
index that is too large or too small.


我的规则是

  • if语句的业务逻辑错误(如代码)
  • 应用程序可以恢复的环境错误的异常
  • 没有恢复的环境错误的未经检查的异常

  • 已检查例外的示例:对于可脱机工作的应用程序,网络已关闭
  • 未经检查的异常的示例:数据库在CRUD Web应用程序上关闭。

有很多关于这个主题的文档。你可以通过浏览Hibernate找到很多东西
网页,因为他们在版本3.x中将Hibernate 2.x的所有异常从已检查更改为未检查


我的感觉是,经过检查的例外是一个有用的合同,应该谨慎使用。我认为检查异常是一个好主意的经典示例是InterruptedException。我的感觉是,我希望能够在我希望它停止时停止线程/进程,无论有人指定Thread.sleep()多长时间。

所以,试图回答你的具体问题,这是你绝对想要确保每个人都处理的问题吗?在我看来,Account没有足够的钱的情况是一个严重的问题,你必须处理它。

回应Peter的评论:这是一个使用InterruptedException作为应该处理的异常的具体情况的例子,你需要有一个有用的默认处理程序。这是我强烈推荐的,当然是在我的实际工作中。你应该至少这样做:

1
2
3
catch (InterruptedException ie) {
    Thread.currentThread().interrupt();
}

该处理程序将确保代码捕获已检查的异常并完全按照您的要求执行:使此线程停止。不可否认,如果上游还有另一个异常处理程序/食者,那么它将不太可能处理异常。即便如此,FindBugs可以帮助您找到这些。

现在,现实开始了:你不一定强迫每个为你的检查异常编写异常处理程序的人都能很好地处理它。也就是说,至少你可以"查找用法"并知道它的使用位置并给出一些建议。

简短形式:如果使用已检查的异常,则会对方法的用户造成负担。确保有充分的理由,建议正确的处理方法并广泛记录所有这些。


我不认为情景(资金不足)保证抛出Exception ---它根本不够特别,应该由程序的正常控制流来处理。但是,如果我真的不得不抛出异常,我会选择一个已检查的异常,通过扩展Exception,而不是RuntimeException,这是未选中的。这迫使我明确地处理异常(我需要声明它被抛出,或者在某处捕获它)。

IllegalArgumentExceptionRuntimeException的子类,使其成为未经检查的异常。如果调用者有一些方便的方法来确定方法参数是否合法,我只会考虑抛出这个。在您的特定代码中,不清楚调用者是否可以访问balance,或者整个"检查余额和转移资金"是否是原子操作(如果不是,那么调用者真的没有方便的方法来验证参数)。

编辑:澄清我投掷IllegalArgumentException的立场。


检查异常的优点是编译器强制开发人员更早地处理它们。在我看来,缺点是开发人员倾向于懒惰和不耐烦,并且存在异常处理代码,以便稍后返回并修复它。当然,这很少发生。

Thinking in Java的作者Bruce Eckel在这个主题上有一篇很好的文章。


简单地说,仅将检查异常用作库的外部合同的一部分,并且仅在客户希望/需要捕获它时使用。请记住,使用已检查的异常时,您会强制自己在呼叫者身上。对于运行时异常,如果它们有详细记录,则可以为调用者提供选择。

已知问题是检查异常在Java中被过度使用,但这并不意味着它们都是坏的。这就是为什么它是Spring哲学中不可或缺的一部分,例如(http://www.springsource.org/about)


行并不总是清楚,但对我来说通常是RuntimeException =编程错误,检查异常=外部错误。这是非常粗略的分类。像其他人说的那样,经过检查的例外情况会迫使你处理,或者至少只考虑一小部分时间。


已检查的异常意味着您的类的客户端被编译器强制处理它。除非他们添加try / catch块,否则他们的代码无法编译。

C#的设计者已经决定优先选择未经检查的异常。

或者,您可以遵循C样式并检查返回值,而不是抛出异常。

如前所述,例外确实有成本,因此它们不应用于控制流程。但他们为他们做的一件事是他们不能被忽视。

如果您决定在这种情况下避开异常,则会冒着类的客户端忽略返回值或在尝试传输之前无法检查余额的风险。

我建议一个未经检查的异常,我会给它一个像InsufficientFundsException这样的描述性名称,以便清楚地知道发生了什么。


我自己,我更喜欢使用经过检查的例外情况。

如果您是API开发人员(后端开发人员),请使用已检查的异常,否则,请使用运行时异常。

另请注意,在某些情况下使用运行时异常会被视为一个大错误,例如,如果要从会话bean中抛出运行时异常(请参阅:http://m-hewedy.blogspot.com/2010/01/ avoid-throwing-runtimeexception-from.html,了解有关在会话bean中使用运行时激活的问题的更多信息)。