了解Java中的检查异常与未检查异常

约书亚·布洛赫在“有效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,什么时候应该创建检查异常,什么时候应该是运行时异常?

何时选择选中和未选中的异常

339370 次浏览

许多人说检查异常(即你应该显式捕获或重新抛出的异常)根本不应该使用。例如,它们在C#中被消除了,大多数语言都没有它们。所以你总是可以抛出RuntimeException的子类(未经检查的异常)

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

这是我对这个话题的扩展观点

至于具体问题:

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

  2. RuntimeException是未经检查的异常吗?
    是的,完全正确。

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

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

  5. 为什么人们在throw子句中添加classException
    大多数情况下,因为人们懒得考虑捕捉什么和重新抛出什么。抛出Exception是一种不好的做法,应该避免。

唉,没有一条规则可以让你决定什么时候捕获,什么时候重新抛出,什么时候使用选中的异常,什么时候使用未选中的异常。我同意这会造成很多混乱和很多糟糕的代码。一般原则由Bloch陈述(你引用了其中的一部分)。一般原则是将异常重新抛出到你可以处理它的层。

  1. 上面的是否被认为是检查异常? 不 如果异常是RuntimeException,则您正在处理异常的事实不会使其成为Checked Exception

  2. RuntimeExceptionunchecked exception吗? 是

Checked Exceptionsjava.lang.Exceptionsubclasses Unchecked Exceptionssubclassesjava.lang.RuntimeException

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

希望这有帮助。

Q:我应该冒泡确切的 使用异常还是屏蔽它?

答:是的,这是一个非常好的问题和重要的设计考虑因素。Exception类是一个非常通用的异常类,可用于包装内部低级异常。你最好创建一个自定义异常并包装在其中。但是,还有一个重要的问题-永远不要掩盖潜在的原始根本原因。对于ex,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 back是否包装在任何应用程序特定的异常中。

关于尝试捕捉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.");
}

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

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-cat。

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

某物是否是“已检查异常”与您是否捕获它或您在catch块中执行的操作无关。它是异常类的属性。任何作为RuntimeExceptionException除了的子类及其子类的东西都是已检查异常。

编译器Java迫使你要么捕获检查异常,要么在方法签名中声明它们。它应该提高程序安全性,但大多数人认为这不值得它造成的设计问题。

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

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

所有这些都是检查异常。未检查异常是RuntimeException的子类。决定不是如何处理它们,而是你的代码应该抛出它们。如果你不希望编译器告诉你你没有处理异常,那么你可以使用未检查(RuntimeException的子类)异常。这些应该保存在你无法恢复的情况下,比如内存溢出错误等。

1)不,NumberFormatException是未经检查的异常。即使您捕获了它(您不需要),因为它未检查。这是因为它是IllegalArgumentException的子类,而RuntimeException的子类。

2)RuntimeException是所有未选中异常的根。RuntimeException的每个子类都未选中。除了错误(位于Throwable下)之外,所有其他异常和Throwable都被选中。

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

5)抛出和捕获'Exception'是一种糟糕的做法。但更一般地说,你可能会抛出其他异常,以便调用者可以决定如何处理它。例如,如果你写了一个库来处理读取一些文件输入,而你的方法被传递了一个不存在的文件,你不知道如何处理。调用者想再次询问还是退出?所以你将异常抛回给调用者。

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

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语言规范中还有一些重要的信息:

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

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

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

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

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

不,总是声明最精确异常可能,或者诸如此类的列表。你声明方法能够抛出的异常是方法和调用者之间契约的一部分。抛出"FileNotFoundException"意味着文件名可能无效,文件将找不到;调用者需要智能地处理这一点。抛出Exception意味着“嘿,发生了。成交。”这是一个非常糟糕的API

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

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

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

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

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

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

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

  • Java区分两类异常(已检查和未检查)。
  • Java强制执行对已检查异常的捕获或声明要求。
  • 异常的类型确定异常是被选中还是未选中。
  • 所有直接或间接的异常类型subclassesRuntimeException 是未经检查的异常。
  • 所有继承自类Exception但不是RuntimeException的类都被认为是checked exceptions
  • 从类Error继承的类被认为是未选中的。
  • 编译器检查每个方法调用和减速以确定是否 方法抛出checked exception
    • 如果是这样,编译器确保异常被捕获或在throw子句中声明。
  • 为了满足catch-or-声明要求的声明部分,生成 异常必须提供包含checked-exceptionthrows子句。
  • Exception类被定义为当它们被认为足够重要以捕获或声明时进行检查。

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

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

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

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

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

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

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

检查异常由JVM在编译时检查,它与资源(文件/db/流/套接字等)相关。检查异常的动机是在编译时,如果资源不可用,应用程序应该定义一个替代行为来处理这个问题。

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

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

检查-可能发生。在编译时检查。

例如…文件操作

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

Eg…

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)

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

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

这是底线准则:如果客户端可以合理地 期望从异常中恢复,使其成为检查异常。如果 客户端不能做任何事情来从异常中恢复,使其成为 未检查的异常

“抛出什么类型的异常”的核心是语义(在某种程度上),上面的引用提供了很好的指导(因此,我仍然被C#摆脱检查异常的想法所震撼-特别是Liskov认为它们的有用性)。

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

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

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

每当涉及检查异常时,方法签名中的某个地方都有一个throws CheckedExceptionCheckedException可以是任何检查异常)。签名不会抛出Exception,抛出Exceptions是实现的一个方面。接口、方法签名、父类,所有这些都不应该依赖于它们的实现。这里使用检查异常(实际上你必须在方法签名中声明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-cat)或声明您正在抛出它(在方法签名中添加throws InterruptedException)。由于我不想在这里捕获这个异常(我希望它向上传播,这样我就可以在其他地方正确处理它),让我们更改签名。

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

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

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

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

这是检查异常不好的一个重要原因。

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

只是为了指出,如果你在代码中抛出一个检查过的异常,而catch只比它高几级,你需要在你和catch之间的每个方法的签名中声明异常。所以,封装被打破了,因为抛出路径中的所有函数都必须知道该异常的细节。

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

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

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

检查异常

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

  • 这些发生在编译时。

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

    假设的例子-假设你要离开你的房子去考试,但是如果你检查你是否把你的大厅门票带回家(编译时间),那么考试大厅(运行时)就不会有任何问题。

未检查异常

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

  • 这些发生在运行时。

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

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

    假设的例子-假设你在考试大厅,但不知何故你的学校发生了火灾事故(意味着在运行时),你当时什么都做不了,但可以在(编译时)之前采取预防措施。

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

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

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

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

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

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