了解 Java 中选中的异常与未选中的异常

Understanding checked vs unchecked exceptions in Java

提问人:Thang Pham 提问时间:5/25/2011 最后编辑:Yogesh Umesh VaityThang Pham 更新时间:10/6/2023 访问量:345276

问:

约书亚·布洛赫(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 中,什么时候应该创建已检查的异常,什么时候应该创建运行时异常?

何时选择选中和未选中的例外

Java 运行时异常 checked-exceptions unchecked-exception

评论

8赞 Erick Robertson 5/25/2011
我有一个很好的未经检查的异常的例子。我有一个类,它保存的数据必须始终保持基于时间的顺序。有一种方法可以将 new 添加到 .如果我的所有代码在整个项目中都能正常工作,则永远不应该将 a 添加到末尾的末尾,该末尾的末尾已经有日期。整个项目中的每个模块都是用这个不言而喻构建的。但是,我会检查此条件,并在发生时抛出未经检查的异常。为什么?如果发生这种情况,我想知道是谁在做这件事并修复它。DataSeriesDataPointDataSeriesDataPoint
3赞 Kaj 5/25/2011
更令人困惑的是。~10 年前,许多人都在提倡检查异常,但现在的观点越来越倾向于“检查异常是坏的”。(但是我不同意这一点)
12赞 Peter Lawrey 5/25/2011
它只在你有用的时候才有用,否则你应该让调用者处理它。记录它并假装它没有发生通常没有用。只是重新扔掉它是没有意义的。包装在 RuntimeException 中并不像某些人想象的那么有用,它只会让编译器停止帮助您。(恕我直言)
53赞 Blessed Geek 1/24/2014
我们应该停止使用检查/未检查的例外的全面误导性术语。它们应称为检查强制非强制检查异常。
3赞 Maveňツ 2/22/2014
我还想过你的第 5 点公共空白method_name抛出异常{}为什么有些人这样做?

答:

518赞 Bozho 5/25/2011 #1

许多人说,根本不应该使用选中的异常(即您应该显式捕获或重新抛出的异常)。例如,它们在 C# 中被淘汰了,大多数语言都没有它们。因此,您始终可以抛出 (unchecked exception) 的子类。RuntimeException

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

这是我对这个话题的扩展看法

至于具体问题:

  1. NumberFormatException 是否被视为已检查的异常?
    不。 未选中 (= 是 的子类)。为什么?我不知道。(但应该有一种方法
    NumberFormatExceptionRuntimeExceptionisValidInteger(..))

  2. RuntimeException 是未经检查的异常吗?
    是的,没错。

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

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

  2. 为什么人们在 throws 子句中添加类 Exception
    最常见的原因是人们懒得考虑要抓住什么和扔掉什么。投掷是一种不好的做法,应该避免。
    Exception

唉,没有一个单一的规则可以让你决定何时捕获,何时重新抛出,何时使用选中的异常以及何时使用未选中的异常。我同意这会导致很多混乱和很多糟糕的代码。布洛赫(Bloch)陈述了一般原则(您引用了其中的一部分)。一般原则是将异常重新抛出到可以处理它的层中。

评论

39赞 Kaj 5/25/2011
关于抛出异常,并不总是因为人们懒惰,当你实现框架时,让框架的用户能够抛出任何异常也是很常见的。例如,您可以在 JSE 中检查 Callable 接口的签名
10赞 Bozho 5/25/2011
@Kaj - 是的,像 Callable、拦截器等一般的东西都是特例。但在大多数情况下,这是因为人们懒惰:)
8赞 Chris 7/25/2012
re: 3.1 “log it and return” 明智地这样做。这非常接近于吃饭或躲藏和例外。我会为不表明问题的事情这样做,这并不是真正的例外。日志很容易被淹没和忽略。
7赞 adrianos 8/24/2012
“当你想强迫你的 API 用户思考如何处理特殊情况时”——如果他们不想,你不能强迫任何人思考。如果他们不想思考,他们会写一个糟糕的异常块,它什么都不做,或者更糟糕的是,删除或干扰关键错误信息。这就是为什么选中的异常是失败的。
3赞 Newtopian 8/12/2016
@adrianos“......你不能强迫任何人思考,如果他们不想......”有了这个思路,我们还可以删除编译错误。我并不是真的在针对你,我一次又一次地听到这个论点,但仍然发现它是将 Checked Exceptions 标记为失败的最糟糕的解释。顺便说一句,我之前已经看到过这样的语言,其中编译(实际上以及运行时错误)实际上通过设计而变得不可能。那条路通向一些非常黑暗的地方。
78赞 d-live 5/25/2011 #2
  1. 上述情况是否被视为已检查的例外情况? 不 您正在处理异常这一事实并不能使它成为 .Checked ExceptionRuntimeException

  2. 是 ? 是的RuntimeExceptionunchecked exception

Checked Exceptions是 的subclassesjava.lang.ExceptionUnchecked Exceptionssubclassesjava.lang.RuntimeException

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

希望这会有所帮助。

问:我应该冒泡吗 exception 还是使用 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

RuntimeException作为一般规则,不应尝试捕获。它们通常表示编程错误,应单独处理。相反,程序员应该在调用某些代码之前检查错误条件,这可能会导致 .例如:RuntimeException

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

这是一种糟糕的编程实践。相反,应该像这样进行 null 检查 -

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。

所以和两者都是,捕获一个应该替换为一个优雅的空检查,而我建议明确地捕获一个以避免可能引入容易出错的代码。NullPointerExceptionNumberFormatExceptionRuntimeExceptionsNullPointerExceptionNumberFormatException

评论

0赞 Thang Pham 5/25/2011
谢谢。还有一个问题,当你冒泡时,我应该冒泡确切的异常还是使用来掩盖它。我在一些遗留代码之上编写代码,然后到处冒泡。我想知道这是否是正确的行为?exceptionExceptionException
1赞 d-live 5/25/2011
这是一个非常好和重要的问题,编辑了我的答案以包括解释。
0赞 Thang Pham 5/25/2011
谢谢。你能给我看一下内容吗?LoginFailureException(sqle)
1赞 d-live 5/25/2011
我没有任何代码,我只是编造了名字等。如果您看到 java.lang.Exception,它有 4 个构造函数,其中两个接受 java.lang.Throwable。在上面的代码片段中,我假设扩展并声明了一个构造函数LoginFailureExceptionExceptionpublic LoginFailureException(Throwable cause) { super(cause) }
0赞 huseyin 2/17/2016
关于该主题的最佳答案。我认为不应该捕获运行时异常,因为这些异常是由于缺乏良好的编程而发生的。我完全同意“蚕食原始根本原因掩盖了无法恢复的实际原因对于生产支持团队来说是一场噩梦,他们只能访问应用程序日志和错误消息。
258赞 Michael Borgwardt 5/25/2011 #3

某件事是否是“已检查的异常”与你是否捕获它或你在捕获块中执行的操作无关。它是异常类的属性。除 and 其子类外,任何子类都是选中的异常。ExceptionRuntimeException

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

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

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

评论

3赞 jeremyjjbrown 12/21/2012
谢谢!我偶尔会从我的方法中抛出例外,因为 crape out 原则中的废话。我团队中的一位开发人员想要输入一个无效的 xpath 表达式,由他们来处理异常。万一他们发现异常并且什么都不做,他们会在代码审查中听到它。
13赞 Bartzilla 12/22/2012
“除了 RuntimeException 及其子类之外,任何属于 Throwable 子类的东西都是经过检查的异常。” - 您的陈述不正确。Error 也继承了 Throwable,并且未选中它。
9赞 Michael Borgwardt 11/15/2013
@JonasEicher:基本上,异常的一个主要优点是,它们允许您选择要在调用堆栈中处理错误的位置(通常非常高),同时保持中间的层完全没有错误处理伪影。选中的异常恰恰破坏了这一优势。另一个问题是,选中/未选中的区别与异常类相关联,该类也代表了异常的概念分类 - 混合了两个根本不可能相关的方面。
2赞 kervin 3/26/2015
“但大多数人的意见似乎是,它不值得它造成的设计问题。
3赞 Bart van Heukelom 11/6/2015
@Bartzilla 是的。为了完整起见,正如 javadoc 所说:“Throwable 和 Throwable 的任何子类,如果不是 RuntimeException 或 Error 的子类,都被视为已检查的异常”Throwable
1赞 mamboking 5/25/2011 #4

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

评论

0赞 eis 8/13/2012
嗯。如果 NumberFormatException 是检查异常,就像你说的那样,它不是与它继承自 RuntimeException 的事实相矛盾吗?
0赞 mamboking 8/13/2012
对不起,我不是很清楚。我指的是FileNotFoundException,而不是NumberFormatException。根据他的 #2 和 #4,他似乎认为 Checked 与 Unchecked 是基于您在捕获异常后如何处理异常。不是它是如何定义的。
10赞 dontocsata 5/25/2011 #5

1) 否,NumberFormatException 是未经检查的异常。即使你抓住了它(你不需要),因为它是未经检查的。这是因为它是一个子类,其子类是 的子类。IllegalArgumentExceptionRuntimeException

2) 是所有未选中的异常的根源。的每个子类都是未选中的。所有其他异常,并被选中,但错误(位于 下)。RuntimeExceptionRuntimeExceptionThrowableThrowable

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

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

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

评论

0赞 Thang Pham 5/25/2011
谢谢。还有一个问题,当你冒泡时,我应该冒泡确切的异常还是使用来掩盖它。我在一些遗留代码之上编写代码,然后到处冒泡。我想知道这是否是正确的行为?exceptionExceptionException
0赞 dontocsata 5/26/2011
你可以让你的方法也抛出异常(这并不理想)。或者捕获 Exception 并抛出更好的 Exception(如 IOException 或其他东西)。所有异常都可以在其构造函数中将 Exception 作为“原因”,因此您应该使用它。
20赞 Aleadam 5/25/2011 #6

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 是已检查的异常。

6 米如果期望调用的方法捕获异常,则可以抛出后者。它只是“传球”。例如,如果您想将其扔到自己的私有方法中,并在公共方法中处理异常。someMethod

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

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

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

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

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

恕我直言,底线是您可以捕获任何 ,但您不需要这样做,事实上,实现不需要维护抛出的相同的未经检查的异常,因为这些异常不是合同的一部分。RuntimeException

评论

0赞 Thang Pham 5/25/2011
谢谢。还有一个问题,当你冒泡时,我应该冒泡确切的异常还是使用来掩盖它。我在一些遗留代码之上编写代码,然后到处冒泡。我想知道这是否是正确的行为?exceptionExceptionException
1赞 Aleadam 5/25/2011
@Harry 我会让比我更了解的人来回答:stackoverflow.com/questions/409563/......
5赞 Tom Dibble 10/2/2012 #7

为了回答最后一个问题(上面的其他问题似乎已经完全回答了),“我应该冒出确切的异常还是使用 Exception 来掩盖它?

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

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

不,始终声明最精确的例外情况,或列出此类情况。将方法声明为能够引发的异常是方法与调用方之间协定的一部分。抛出意味着文件名可能无效,并且找不到文件;呼叫者需要明智地处理这个问题。投掷的意思是“嘿,嘘,发生了。交易。这是一个非常可怜的."FileNotFoundException"ExceptionAPI

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

评论

0赞 Salvador Valencia 9/22/2016
确切地说,将您的检查异常声明作为代码文档的一部分,并帮助使用您的软件的人。
-1赞 Slawomir 4/8/2014 #8

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

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

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

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

评论

4赞 Warren Dew 5/29/2014
因为只有大多数调用方,而不是所有调用方,都应该换行并重新抛出。选中异常这一事实意味着调用方必须考虑他们是“最多”的调用者之一,还是能够并且应该处理异常的少数调用者之一。
1赞 Slawomir 5/30/2014
如果你喜欢检查你所做的每个调用的错误,请“返回”到C语言。 异常是一种将正常程序执行与异常程序执行分开的方法,而不会污染你的代码。异常可确保您无法在某种程度上以静默方式忽略错误。
3赞 JAVA 8/4/2014 #9

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

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

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

评论

0赞 Salvador Valencia 9/22/2016
好点子。这意味着将其膨胀到最负责的层,该层控制逻辑流并监督应用程序的业务逻辑。例如,数据库层不可能向客户端传达关键内容缺失或无响应。当它冒泡到最顶层的服务器层时,可以直接刷新客户端的视图并显示严重错误消息。
3赞 tokhi 10/3/2014 #10
  • Java 区分了两类异常(选中和未选中)。
  • Java 对已检查的异常强制执行 catch 或声明要求。
  • 异常的类型决定了异常是选中还是未选中。
  • 所有直接或间接的类异常类型都是未选中的异常。subclassesRuntimeException
  • 所有继承自 class 但不是 的类都被视为 。ExceptionRuntimeExceptionchecked exceptions
  • 继承自 Error 类的类被视为未选中。
  • 编译器检查每个方法调用和减速,以确定 方法抛出 。checked exception
    • 如果是这样,编译器将确保捕获异常或在 throws 子句中声明异常。
  • 为了满足 catch-or-declare 要求的 declare 部分,生成的方法 例外必须提供包含 .throwschecked-exception
  • 异常类被定义为在被认为足够重要以捕获或声明时进行检查。
2赞 rghome 1/15/2015 #11

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

以你的类为例,想象为它设计一个接口,使该接口描述类的功能,但不描述底层实现(接口应该如此)。假装你可能以另一种方式实现该类。

查看接口的方法,并考虑它们可能引发的异常:

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

如果异常是由基础实现引起的,则不应在接口中。因此,它必须是类中未选中的异常(因为未选中的异常不需要出现在接口签名中),或者必须将其包装并作为作为接口方法一部分的已检查异常重新引发。

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

您通常不应该计划“冒泡”异常(捕获和重新抛出)。异常应由调用方处理(在这种情况下,它被检查),或者它应该一直到高级处理程序(在这种情况下,如果未选中它,则最容易)。

6赞 Jatinder Pal 7/13/2015 #12

选中的异常由 JVM 及其与资源(files/db/stream/socket 等)相关的资源在编译时检查。检查异常的动机是,在编译时,如果资源不可用,应用程序应定义另一种行为来在 catch/finally 块中处理此问题。

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

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

9赞 bharanitharan 7/25/2015 #13

选中 - 容易发生。已在编译时选中。

例如..文件操作

未选中 - 由于数据错误。已签入运行时。

例如..

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)

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

5赞 Thomas 9/9/2015 #14

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

这是底线准则:如果客户可以合理地 预期从异常中恢复,使其成为已检查的异常。如果 客户端无法执行任何操作来从异常中恢复,请将其设置为 未选中的异常

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

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

5赞 Dan Moldovan 11/6/2015 #15

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

3赞 Blueriver 8/17/2016 #16

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

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

让我举个例子。

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

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");
    }
}

Class 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 like 进行编程,然后决定要使用哪个实现,例如 .如果 Bar 的方法 foo() 没有覆盖 IFoo 的方法 foo,当我这样做时,它不会调用 Bar 的 foo() 实现。IFoo foo;foo = new Bar();foo.foo();

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

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

这是检查异常是坏的一个重要原因。大写。

一种解决方案是捕获已检查的异常,将其包装在未检查的异常中,然后引发未检查的异常。

评论

2赞 Salvador Valencia 9/22/2016
是的,这很糟糕,因为你说你不想抓住它。但为了防止影响IFOO的签名,您必须这样做。我宁愿这样做并继续前进,而不是重新平衡我所有的接口签名,以避免捕获异常(仅仅因为异常是坏的)。
0赞 Blueriver 9/22/2016
是的,我同意。我有点不清楚。我想要一个异常来传播,这样我就可以在其他地方处理它。一种解决方案是捕获 InterruptedException 并引发未经检查的异常。也就是说,我们避免检查异常并传递未检查的异常(即使它们仅作为包装器有意义)
0赞 Flyout91 2/14/2018
“但是,这将导致我的 Foo 类出现问题,因为它是 foo() 方法的签名与 IFoo 的方法签名不同”。我刚刚测试了你的想法,即使我们添加到 IFoo 的方法签名中,也可以进行编译,而不会在任何实现中抛出任何东西。所以它不会真正引起任何问题。如果在接口中,您让每个方法签名抛出 ,它只是让实现选择抛出或不抛出异常(任何异常,因为封装所有异常)。throws InterruptedExceptionExceptionException
0赞 Flyout91 2/14/2018
但是,我承认这可能会令人困惑,因为当您实现这样的 inteface 并单击类似“添加未实现的方法”之类的内容时,它们将自动创建带有签名中的子句,即使您的实现不会抛出任何东西或可能是一个更具体的异常。但我仍然觉得始终为 Interface 的方法抛出 Exception 是一种很好的做法,因为它再次让用户可以选择抛出或不抛出任何东西。throw Exception
0赞 erickson 12/12/2018
这错过了整个要点。接口的目的是声明客户端需要满足的合约。这可能包括它能够处理的故障场景。当实现遇到错误时,它应将该错误映射到客户端接口声明的相应抽象故障。
2赞 Zoran Pandovski 10/19/2016 #17

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

9赞 Omer 12/4/2016 #18

运行时异常: 运行时异常称为未经检查的异常。所有其他例外情况 是选中的异常,它们不是派生自 java.lang.RuntimeException。

已检查的异常: 选中的异常必须在代码中的某个位置捕获。如果调用 引发已检查异常但未捕获已检查异常的方法 在某个地方,您的代码将无法编译。这就是为什么它们被称为检查的原因 异常 :编译器检查以确保它们被处理或声明。

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

0赞 Varun Vashista 1/10/2018 #19

已检查的异常

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

  • 这些发生在编译时。

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

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

未选中的异常

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

  • 这些发生在运行时。

  • 如果这些异常处理不当,它们不会产生编译时错误。但是该程序将在运行时过早终止。

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

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

-1赞 shawnhcorey 2/1/2018 #20

必须检查所有异常。

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

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

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

2赞 Ozgen 3/20/2018 #21

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

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

评论

0赞 Pang 10/6/2023
答案中的链接 - “此视频不再可用”