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

时间:2009-01-31 19:57:17

标签: java exception

  

可能重复:
  When to choose checked and unchecked exceptions

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

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

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

14 个答案:

答案 0 :(得分:102)

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

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

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

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

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

答案 1 :(得分:42)

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

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

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

答案 2 :(得分:34)

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

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

try {
  ...
} catch (IOException e) {
  // do nothing
}

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

try {
  ...
} catch (IOException e) {
  throw new RuntimeExceptione(e);
}

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

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

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

此外,checked exceptions break encapsulation

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

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

答案 3 :(得分:8)

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

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

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

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

<强> 是建设性的编辑:

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

在这种情况下,你已经连续拥有所有的鸭子,并且知道你无能为力(因为true变成了false,就好像它本身一样),而不是日志例外情况,向某人发送通知,并礼貌地告诉客户有人在上一次满月期间没有正确牺牲他们的处女,问题将在第一时间得到解决。

<强> 少-enterprisey暗示编辑:

如果你这样做是为了你自己的乐趣(这种情况似乎就是这个,请参阅评论),我建议你回复一个布尔值。用法如下:

// ...
boolean success = transferTo(otherAccount, ONE_MILLION_DOLLARS_EXCLAMATION);

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

答案 4 :(得分:8)

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

答案 5 :(得分:6)

我的规则是

  • if 业务逻辑错误语句(如代码)
  • 应用可以恢复
  • 的环境错误的例外情况
  • 针对无恢复

    的环境错误的未经检查的例外情况
    1. 已检查例外的示例:对于可脱机工作的应用程序,网络已关闭
    2. 未经核心的异常示例:数据库已关闭CRUD Web应用程序。

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

答案 6 :(得分:6)

来自Unchecked Exceptions -- The Controversy

  

如果可以合理地预期客户   从异常中恢复,成功   检查的例外。如果是客户   无法从中恢复过来   例外,让它不受控制   异常。

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

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

  

运行时异常代表问题   这是编程的结果   问题,以及API客户端   代码不能合理地预期   从中恢复或处理它们   无论如何。这些问题包括   算术异常,例如   除以零;指针异常,   例如尝试访问对象   通过空引用;和索引   异常,例如尝试   通过一个访问数组元素   索引太大或太小。

答案 7 :(得分:5)

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

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

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

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

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

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

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

答案 8 :(得分:3)

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

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

或者你可以遵循C风格并检查返回值而不是抛出异常。

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

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

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

答案 9 :(得分:3)

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

答案 10 :(得分:3)

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

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

答案 11 :(得分:3)

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

Thinking in Java 的作者Bruce Eckel在这个主题上有一个很好的essay

答案 12 :(得分:3)

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

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

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

答案 13 :(得分:2)

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

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

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