743

Joshua Bloch 在“ Effective Java ”中说

对可恢复条件使用检查异常,对编程错误使用运行时异常(第 2 版第 58 条)

让我们看看我是否理解正确。

这是我对已检查异常的理解:

try{
    String userInput = //read in user input
    Long id = Long.parseLong(userInput);
}catch(NumberFormatException e){
    id = 0; //recover the situation by setting the id to 0
}

1.以上是否被视为已检查异常?

2. RuntimeException 是未经检查的异常吗?

这是我对未经检查的异常的理解:

try{
    File file = new File("my/file/path");
    FileInputStream fis = new FileInputStream(file);   
}catch(FileNotFoundException e){

//3. What should I do here?
    //Should I "throw new FileNotFoundException("File not found");"?
    //Should I log?
    //Or should I System.exit(0);?
}

4.现在,上面的代码不能也是检查异常吗?我可以尝试恢复这种情况吗?我可以吗?(注意:我的第三个问题在catch上面)

try{
    String filePath = //read in from user input file path
    File file = new File(filePath);
    FileInputStream fis = new FileInputStream(file);   
}catch(FileNotFoundException e){
    //Kindly prompt the user an error message
    //Somehow ask the user to re-enter the file path.
}

5. 人们为什么要这样做?

public void someMethod throws Exception{

}

为什么他们让异常冒泡?越早处理错误不是更好吗?为什么要冒泡?

6. 我应该冒泡确切的异常还是使用异常来掩盖它?

以下是我的阅读

在 Java 中,什么时候应该创建已检查异常,什么时候应该创建运行时异常?

何时选择已检查和未检查的异常

4

21 回答 21

508

许多人说根本不应该使用已检查的异常(即您应该明确捕获或重新抛出的异常)。例如,它们在 C# 中被淘汰了,而大多数语言都没有它们。所以你总是可以抛出RuntimeException(unchecked exception)的子类

但是,我认为检查的异常很有用——当你想强制你的 API 的用户思考如何处理异常情况(如果它是可恢复的)时使用它们。只是检查异常在Java平台中被过度使用,让人讨厌。

这是我对该主题的扩展视图

至于具体问题:

  1. 是否NumberFormatException考虑检查异常?
    No.NumberFormatException未选中(= 是 的子类RuntimeException)。为什么?我不知道。(但应该有一种方法isValidInteger(..)

  2. RuntimeException未经检查的异常吗?
    对,就是这样。

  3. 我应该在这里做什么?
    这取决于此代码在哪里以及您希望发生什么。如果它在 UI 层 - 抓住它并显示警告;如果它在服务层中 - 根本不要抓住它 - 让它冒泡。只是不要吞下例外。如果在大多数情况下发生异常,您应该选择以下之一:

    • 记录并返回
    • 重新抛出它(声明它由方法抛出)
    • 通过在构造函数中传递当前异常来构造一个新异常
  4. 现在,上面的代码不能也是一个检查异常吗?我可以尝试恢复这种情况吗?我可以吗?
    本来可以的。但是没有什么能阻止你捕捉未经检查的异常

  5. 为什么人们Exception在 throws 子句中添加类?
    最常见的原因是人们懒于考虑捕捉什么以及重新抛出什么。投掷Exception是一种不好的做法,应该避免。

唉,没有单一的规则可以让您确定何时捕获、何时重新抛出、何时使用已检查的异常以及何时使用未经检查的异常。我同意这会导致很多混乱和很多糟糕的代码。Bloch 陈述了一般原则(您引用了其中的一部分)。一般原则是向可以处理的层重新抛出异常。

于 2011-05-24T19:49:42.087 回答
253

某事是否是“已检查异常”与您是否捕获它或您在 catch 块中所做的事情无关。它是异常类的属性。任何属于Exception except forRuntimeException及其子类的子类都是已检查异常。

Java 编译器强制您捕获已检查的异常或在方法签名中声明它们。它本应提高程序的安全性,但大多数人的意见似乎是,它产生的设计问题不值得。

为什么他们让异常冒泡?处理错误不是越快越好吗?为什么要冒泡?

因为这就是例外的全部意义所在。如果没有这种可能性,您将不需要例外。它们使您能够在您选择的级别处理错误,而不是强迫您在最初发生的低级别方法中处理它们。

于 2011-05-24T19:53:17.170 回答
79
  1. 以上是否被视为已检查异常?否 您正在处理异常的事实并不能使其成为 aChecked Exception如果它是 a RuntimeException

  2. RuntimeException一个unchecked exception?是的

Checked Exceptionssubclassesjava.lang.Exception Unchecked Exceptionssubclassesjava.lang.RuntimeException

抛出检查异常的调用需要包含在 try{} 块中或在方法调用者的更高级别中处理。在这种情况下,当前方法必须声明它抛出所述异常,以便调用者可以做出适当的安排来处理异常。

希望这可以帮助。

问:我应该冒泡确切的异常还是使用 Exception 掩盖它?

答:是的,这是一个非常好的问题,也是重要的设计考虑因素。Exception类是一个非常通用的异常类,可以用来包装内部低级异常。您最好创建一个自定义异常并将其包装在其中。但是,还有一个大问题 - 永远不要掩盖潜在的原始根本原因。例如,Don't ever请执行以下操作 -

try {
     attemptLogin(userCredentials);
} catch (SQLException sqle) {
     throw new LoginFailureException("Cannot login!!"); //<-- Eat away original root cause, thus obscuring underlying problem.
}

而是执行以下操作:

try {
     attemptLogin(userCredentials);
} catch (SQLException sqle) {
     throw new LoginFailureException(sqle); //<-- Wrap original exception to pass on root cause upstairs!.
}

吃掉最初的根本原因会掩盖无法恢复的实际原因,这对于生产支持团队来说是一场噩梦,他们只能访问应用程序日志和错误消息。尽管后者是一个更好的设计,但很多人并不经常使用它,因为开发人员只是未能将底层消息传递给调用者。因此,请牢记:Always pass on the actual exception无论是否包含在任何特定于应用程序的异常中。

在尝试捕捉RuntimeExceptions

RuntimeExceptions 作为一般规则不应被尝试捕获。它们通常表示编程错误,应该不理会。相反,程序员应该在调用一些可能导致RuntimeException. 例如:

try {
    setStatusMessage("Hello Mr. " + userObject.getName() + ", Welcome to my site!);
} catch (NullPointerException npe) {
   sendError("Sorry, your userObject was null. Please contact customer care.");
}

这是一种糟糕的编程习惯。相反,应该像这样进行空检查 -

if (userObject != null) {
    setStatusMessage("Hello Mr. " + userObject.getName() + ", Welome to my site!);
} else {
   sendError("Sorry, your userObject was null. Please contact customer care.");
}

但是有时这种错误检查很昂贵,例如数字格式,请考虑一下 -

try {
    String userAge = (String)request.getParameter("age");
    userObject.setAge(Integer.parseInt(strUserAge));
} catch (NumberFormatException npe) {
   sendError("Sorry, Age is supposed to be an Integer. Please try again.");
}

在这里调用前错误检查是不值得的,因为它本质上意味着在 parseInt() 方法中复制所有字符串到整数的转换代码 - 如果由开发人员实现,则容易出错。所以最好不要使用try-catch。

NullPointerException两者NumberFormatException都是RuntimeExceptions,捕获 aNullPointerException应该替换为优雅的空检查,而我建议显式捕获 a以NumberFormatException避免可能引入容易出错的代码。

于 2011-05-24T19:50:47.547 回答
20

1. 如果您不确定异常,请检查 API:

 java.lang.Object
 extended by java.lang.Throwable
  extended by java.lang.Exception
   extended by java.lang.RuntimeException  //<-NumberFormatException is a RuntimeException  
    extended by java.lang.IllegalArgumentException
     extended by java.lang.NumberFormatException

2. 是的,以及扩展它的每个例外。

3. 无需捕获和抛出相同的异常。在这种情况下,您可以显示一个新的文件对话框。

4. FileNotFoundException已经一个检查异常。

5. 如果期望调用的方法someMethod捕获异常,则可以抛出后者。它只是“传球”。它使用的一个例子是如果你想把它扔到你自己的私有方法中,并在你的公共方法中处理异常。

一个很好的阅读是 Oracle 文档本身:http: //download.oracle.com/javase/tutorial/essential/exceptions/runtime.html

为什么设计者决定强制一个方法指定可以在其范围内抛出的所有未捕获的检查异常?方法可以抛出的任何异常都是该方法的公共编程接口的一部分。调用方法的人必须知道方法可以抛出的异常,以便他们可以决定如何处理它们。这些异常与其参数和返回值一样,都是该方法编程接口的一部分。

下一个问题可能是:“如果记录方法的 API 非常好,包括它可以抛出的异常,为什么不指定运行时异常呢?” 运行时异常表示由编程问题引起的问题,因此,不能合理地期望 API 客户端代码从它们中恢复或以任何方式处理它们。此类问题包括算术异常,例如除以零;指针异常,例如试图通过空引用访问对象;和索引异常,例如尝试通过太大或太小的索引访问数组元素。

Java 语言规范中还有一些重要的信息:

throws 子句中命名的检查异常类是方法或构造函数的实现者和用户之间契约的一部分

恕我直言,底线是您可以捕获 any RuntimeException,但您不需要,实际上不需要实现来维护抛出的相同的非检查异常,因为这些不是合同的一部分。

于 2011-05-24T20:03:41.627 回答
10

1) 不,NumberFormatException 是未经检查的异常。即使你抓住了它(你不是必须的),因为它没有被检查。这是因为它是 的子类的IllegalArgumentException子类RuntimeException

2)RuntimeException是所有未检查异常的根源。的每个子类RuntimeException都未选中。所有其他异常Throwable都被检查,除了错误(在下面Throwable)。

3/4)您可以提醒用户他们选择了一个不存在的文件并要求一个新文件。或者只是退出通知用户他们输入了无效的内容。

5)投掷和接球'Exception'是不好的做法。但更一般地说,您可能会抛出其他异常,以便调用者可以决定如何处理它。例如,如果您编写了一个库来处理读取某些文件输入,并且您的方法传递了一个不存在的文件,那么您不知道如何处理它。来电者是想再次询问还是退出?因此,您将异常向上抛出给调用者。

在许多情况下,unchecked Exception因为程序员没有验证输入(在NumberFormatException您的第一个问题中),所以会发生。这就是为什么捕获它们是可选的,因为有更优雅的方法可以避免生成这些异常。

于 2011-05-24T19:56:40.947 回答
8

检查 - 容易发生。签入编译时间。

例如.. FileOperations

未选中 - 由于数据错误。在运行时检查。

例如..

String s = "abc";
Object o = s;
Integer i = (Integer) o;

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at Sample.main(Sample.java:9)

这里的异常是由于错误的数据造成的,并且在编译时无法确定。

于 2015-07-24T16:58:37.430 回答
7

运行时异常:运行时异常指的是未经检查的异常。所有其他异常都是检查异常,它们不是从 java.lang.RuntimeException 派生的。

Checked Exceptions:必须在代码中的某处捕获已检查的异常。如果您调用的方法引发了已检查的异常,但您没有在某处捕获已检查的异常,则您的代码将无法编译。这就是为什么它们被称为检查异常:编译器检查以确保它们被处理或声明。

Java API 中的许多方法都会抛出已检查的异常,因此您经常会编写异常处理程序来处理由您未编写的方法生成的异常。

于 2016-12-03T17:40:39.277 回答
6

要回答最后一个问题(其他问题似乎在上面得到了彻底的回答),“我应该冒泡确切的异常还是使用异常来掩盖它?”

我假设你的意思是这样的:

public void myMethod() throws Exception {
    // ... something that throws FileNotFoundException ...
}

不,总是声明最精确的例外情况,或者这样的列表。你声明你的方法能够抛出的异常是你的方法和调用者之间契约的一部分。抛出"FileNotFoundException"意味着文件名可能无效并且找不到文件;调用者需要智能地处理它。投掷Exception的意思是“嘿,事情发生了。交易。” 这是一个很差的API

在第一篇文章的评论中有一些例子,其中“throws Exception”是一个有效且合理的声明,但对于normal您将编写的大多数“”代码来说,情况并非如此。

于 2012-10-01T21:37:35.430 回答
6

JVM 在编译时检查已检查的异常及其与资源(文件/数据库/流/套接字等)相关的内容。检查异常的动机是,在编译时,如果资源不可用,应用程序应该定义一个替代行为来在 catch/finally 块中处理这个问题。

未经检查的异常是纯粹的程序错误、错误的计算、空数据甚至业务逻辑中的故障都可能导致运行时异常。在代码中处理/捕获未经检查的异常绝对没问题。

解释取自http://coder2design.com/java-interview-questions/

于 2015-07-13T13:13:19.723 回答
6

我认为检查的异常对于使用外部库的开发人员来说是一个很好的提醒,即在异常情况下该库中的代码可能会出错。

于 2015-11-06T08:25:50.463 回答
5

最喜欢的关于未检查异常和检查异常之间区别的描述由 Java 教程跟踪文章“未检查异常 - 争议”提供(很抱歉在这篇文章中获得了所有基本知识 - 但是,嘿,基础知识有时是最好的):

这是底线准则:如果可以合理地期望客户端从异常中恢复,则将其设为已检查异常。如果客户端无法从异常中恢复,请将其设为未经检查的异常

“抛出什么类型的异常”的核心是语义(在某种程度上),上面的引用提供了很好的指导(因此,我仍然对 C# 摆脱检查异常的概念感到震惊 - 特别是 Liskov 主张它们的用处)。

然后剩下的就变得合乎逻辑了:编译器希望我明确地响应哪些异常?您希望客户从中恢复的那些。

于 2015-09-08T21:21:32.863 回答
3

为什么他们让异常冒泡?越早处理错误不是更好吗?为什么要冒泡?

例如,假设您有一些客户端-服务器应用程序,并且客户端请求了一些无法找到的资源,或者在处理用户请求时可能在服务器端发生了其他错误,那么这是职责服务器告诉客户端为什么他不能得到他请求的东西,所以为了在服务器端实现这一点,编写代码以使用throw关键字抛出异常而不是吞下或处理它。如果服务器处理它/吞下它,那么就没有机会向客户暗示发生了什么错误。

注意:为了清楚地描述发生的错误类型,我们可以创建自己的异常对象并将其抛出给客户端。

于 2014-08-04T10:22:39.507 回答
3
  • Java 区分两类异常(已检查和未检查)。
  • Java 对检查的异常强制执行捕获或声明的要求。
  • 异常的类型决定了异常是被检查还是未被检查。
  • 所有直接或间接subclasses属于类的异常类型RuntimeException 都是未经检查的异常。
  • Exception从 class 继承但不是继承的所有类RuntimeException都被认为是checked exceptions.
  • 从类 Error 继承的类被认为是未选中的。
  • 编译器检查每个方法调用和减速以确定方法是否抛出checked exception
    • 如果是这样,编译器确保异常被捕获或在 throws 子句中声明。
  • 为了满足 catch-or-declare 要求的声明部分,生成异常的方法必须提供一个throws包含checked-exception.
  • Exception类被定义为在它们被认为足够重要以捕获或声明时进行检查。
于 2014-10-02T20:21:54.543 回答
3

我只是想添加一些根本不使用检查异常的理由。这不是一个完整的答案,但我觉得它确实回答了您的部分问题,并补充了许多其他答案。

每当涉及已检查异常时,throws CheckedException方法签名中都会有一个地方(CheckedException可能是任何已检查异常)。签名不会抛出异常,抛出异常是实现的一个方面。接口、方法签名、父类,所有这些都不应该依赖于它们的实现。此处使用已检查异常(实际上您必须throws在方法签名中声明 的事实)将您的高级接口与这些接口的实现绑定。

让我给你看一个例子。

让我们有一个像这样漂亮干净的界面

public interface IFoo {
    public void foo();
}

现在我们可以编写许多方法的实现foo(),比如这些

public class Foo implements IFoo {
    @Override
    public void foo() {
        System.out.println("I don't throw and exception");
    }
}

Foo 类非常好。现在让我们在 Bar 类中进行第一次尝试

public class Bar implements IFoo {
    @Override
    public void foo() {
        //I'm using InterruptedExcepton because you probably heard about it somewhere. It's a checked exception. Any checked exception will work the same.
        throw new InterruptedException();
    }
}

这个类 Bar 不会编译。由于 InterruptedException 是一个检查异常,您必须捕获它(在方法 foo() 中使用 try-catch)或声明您正在抛出它(添加throws InterruptedException到方法签名中)。由于我不想在这里捕获此异常(我希望它向上传播,以便我可以在其他地方正确处理它),让我们更改签名。

public class Bar implements IFoo {
    @Override
    public void foo() throws InterruptedException {
        throw new InterruptedException();
    }
}

这个类 Bar 也不会编译!Bar 的 foo() 方法不会覆盖 IFoo 的 foo() 方法,因为它们的签名不同。我可以删除 @Override 注释,但我想针对接口 IFoo 进行编程,IFoo foo;然后再决定要使用哪个实现,例如foo = new Bar();. 如果 Bar 的方法 foo() 没有覆盖 IFoo 的方法 foo,那么当我这样做时,foo.foo();它不会调用 Bar 的 foo() 实现。

为了让 Barpublic void foo() throws InterruptedException覆盖 IFoo,public void foo()我必须添加throws InterruptedException到 IFoo 的方法签名中。但是,这会导致我的 Foo 类出现问题,因为它的 foo() 方法的签名与 IFoo 的方法签名不同。此外,如果我添加throws InterruptedException到 Foo 的方法 foo() 中,我会得到另一个错误,说明 Foo 的方法 foo() 声明它抛出了一个 InterruptedException 但它从不抛出一个 InterruptedException。

正如你所看到的(如果我在解释这些东西方面做得不错的话),我抛出一个像 InterruptedException 这样的检查异常的事实迫使我将我的接口 IFoo 绑定到它的一个实现,这反过来又会对 IFoo 造成严重破坏其他实现!

这是检查异常不好的一大原因。大写。

一种解决方案是捕获已检查的异常,将其包装在未检查的异常中并抛出未检查的异常。

于 2016-08-17T13:30:23.267 回答
2

这是一个简单的规则,可以帮助您做出决定。它与如何在 Java 中使用接口有关。

拿你的类,想象为它设计一个接口,这样接口描述了类的功能,但没有描述底层实现(作为接口应该)。假设您可能会以另一种方式实现该类。

查看接口的方法并考虑它们可能抛出的异常:

如果一个方法可以抛出异常,无论底层实现如何(换句话说,它只描述功能),那么它可能应该是接口中的检查异常。

如果异常是由底层实现引起的,那么它不应该在接口中。因此,它必须是您的类中的未经检查的异常(因为未经检查的异常不需要出现在接口签名中),或者您必须将其包装并重新抛出作为接口方法的一部分的经检查的异常。

要决定是否应该换行并重新抛出,您应该再次考虑接口的用户是否必须立即处理异常条件,或者异常是如此普遍以至于您无能为力而应该向上传播堆栈。当表示为您正在定义的新接口的功能时,包装的异常是否有意义,或者它只是一袋可能发生在其他方法上的可能错误条件的载体?如果是前者,它可能仍然是一个已检查的异常,否则它应该是未检查的。

您通常不应该计划“冒泡”异常(捕获并重新抛出)。异常应该由调用者处理(在这种情况下它被检查)或者它应该一直到高级处理程序(在这种情况下,如果它不被检查是最简单的)。

于 2015-01-15T09:45:45.987 回答
2

需要指出的是,如果你在代码中抛出了一个已检查的异常并且catch在上面几级,你需要在你和catch之间的每个方法的签名中声明这个异常。因此,封装被破坏了,因为 throw 路径中的所有函数都必须知道该异常的详细信息。

于 2016-10-18T23:09:56.207 回答
2

简而言之,您上面的一个或多个模块应该在运行时处理的异常称为检查异常;其他是未经检查的异常,它们是RuntimeExceptionError

在此视频中,它解释了 Java 中的已检查和未检查异常:
https ://www.youtube.com/watch?v=ue2pOqLaArw

于 2018-03-19T23:59:03.933 回答
1

所有这些都是检查异常。未经检查的异常是 RuntimeException 的子类。决定不在于如何处理它们,而在于您的代码是否应该抛出它们。如果您不希望编译器告诉您尚未处理异常,则使用未经检查的(RuntimeException 的子类)异常。这些应该保存在您无法恢复的情况下,例如内存不足错误等。

于 2011-05-24T19:53:43.263 回答
0

检查异常

  • 编译器在运行时检查程序是否顺利执行的异常称为检查异常。

  • 这些发生在编译时。

  • 如果这些处理不当,它们将给出编译时错误(Not Exception)。
  • 除了 RuntimeException 之外的 Exception 类的所有子类都是 Checked Exception。

    假设示例- 假设您要离开家去参加考试,但如果您检查您是否在家拿了 Hall Ticket(编译时),那么在考试大厅(运行时)就不会有任何问题。

未经检查的异常

  • 未经编译器检查的异常称为未检查异常。

  • 这些发生在运行时。

  • 如果这些异常没有正确处理,它们不会给出编译时错误。但是程序会在运行时提前终止。

  • RunTimeException 和 Error 的所有子类都是未经检查的异常。

    假设示例- 假设您在考场,但不知何故您的学校发生了火灾事故(意味着在运行时),您当时无能为力,但可以在之前(编译时)采取预防措施。

于 2018-01-09T17:56:17.080 回答
-1

如果有人关心另一个不喜欢检查异常的证据,请参阅流行的 JSON 库的前几段:

“虽然这是一个检查异常,但它很少可恢复。大多数调用者应该简单地将这个异常包装在一个未经检查的异常中并重新抛出:”

那么,如果我们应该“简单地包装它”,为什么会有人让开发人员继续检查异常呢?哈哈

http://developer.android.com/reference/org/json/JSONException.html

于 2014-04-08T02:58:23.750 回答
-2

所有异常都必须是检查异常。

  1. 未经检查的异常是不受限制的 goto。不受限制的 goto 被认为是一件坏事。

  2. 未经检查的异常会破坏封装。为了正确处理它们,必须知道 thrower 和 catcher 之间的调用树中的所有函数以避免错误。

  3. 异常是抛出它们的函数中的错误,而不是处理它们的函数中的错误。异常的目的是通过将是否是错误的决定推迟到另一个上下文来给程序第二次机会。只有在其他情况下才能做出正确的决定。

于 2018-02-01T12:36:08.260 回答