Java:已检查vs未经检查的异常说明

Java: checked vs unchecked exception explanation

我在StackOverflow上阅读了多篇关于选中与未选中异常的文章。老实说,我仍然不太确定如何正确使用它们。

Joshua Bloch在"有效爪哇"说

Use checked exceptions for
recoverable conditions and runtime
exceptions for programming errors
(Item 58 in 2nd edition)

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

以下是我对检查异常的理解:

1
2
3
4
5
6
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是否为未选中的异常?

以下是我对未检查异常的理解:

1
2
3
4
5
6
7
8
9
10
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中)

1
2
3
4
5
6
7
8
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。为什么人们要这样做?

1
2
3
public void someMethod throws Exception{

}

为什么他们会让例外情况浮出水面?处理错误不是越早越好吗?为什么泡沫?

编辑:我应该冒泡出确切的异常,还是用异常屏蔽它?

以下是我的阅读资料

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

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


许多人说,检查过的异常(即应该显式捕获或重新引发的异常)根本不应该使用。例如,它们在C语言中被排除,而大多数语言没有它们。因此,您总是可以抛出EDOCX1的子类〔0〕(未选中的异常)

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

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

对于具体问题:

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

  • RuntimeException是否为未经检查的异常?是的,没错。

  • 我该怎么办?这取决于代码在哪里以及您希望发生什么。如果它在UI层中-捕捉它并显示警告;如果它在服务层中-不要捕捉它-让它冒泡。只是不要接受例外。如果在大多数情况下发生异常,则应选择以下选项之一:

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

  • 为什么人们在throws子句中添加类Exception?最常见的原因是人们懒得考虑捕捉什么和重新传播什么。投掷Exception是一种不好的做法,应该避免。

  • 唉,没有一条规则可以让您决定何时捕获、何时重新引发、何时使用选中的异常以及何时使用未选中的异常。我同意这会导致很多混乱和很多错误的代码。总原则由布洛赫提出(你引用了其中的一部分)。一般的原则是将异常重新发送到可以处理它的层。


    什么是"选中的异常"与是否捕获它或在catch块中执行的操作无关。它是异常类的属性。除RuntimeException及其子类外,任何属于Exception子类的内容都是检查异常。

    Java编译器强制您捕获检查异常,或者在方法签名中声明它们。它本来是为了提高程序的安全性,但大多数人认为它不值得它所造成的设计问题。

    Why do they let the exception bubble
    up? Isnt handle error the sooner the
    better? Why bubble up?

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


  • 以上是否被视为检查异常?不如果您处理的是一个异常,那么它不会成为一个Checked Exception,如果它是一个RuntimeException

  • RuntimeExceptionunchecked exception吗?是的

  • Checked Exceptionsjava.lang.ExceptionsubclassesUnchecked Exceptionsjava.lang.RuntimeExceptionsubclasses

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

    希望这有帮助。

    Q: should I bubble up the exact
    exception or mask it using Exception?

    A:是的,这是一个很好的问题,也是重要的设计考虑。类异常是一个非常普通的异常类,可以用来包装内部的低级异常。您最好创建一个自定义异常并将其包装在其中。但是,还有一个大问题——从来没有在根本原因上被掩盖过。对于Ex,Don't ever执行以下操作-

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

    而是执行以下操作:

    1
    2
    3
    4
    5
    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返回,无论是否包含在任何特定于应用程序的异常中。

    On try-catching RuntimeExceptions

    作为一般规则,不应试图抓住RuntimeExceptions。它们通常表示编程错误,应该单独使用。相反,程序员应该在调用一些可能导致RuntimeException的代码之前检查错误情况。对于EX:

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

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

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

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

    1
    2
    3
    4
    5
    6
    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()方法中复制所有字符串到整数的转换代码——如果由开发人员实现,则很容易出错。所以,最好还是放弃试捕。

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


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

    1
    2
    3
    4
    5
    6
     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。是的,以及所有扩展它的异常。

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

    4。FileNotFoundException已是选中的异常。

    5。如果期望调用someMethod的方法捕获异常,则可以抛出后者。它只是"传球"。如果您想将它抛出到自己的私有方法中,并改为在公共方法中处理异常,那么使用它的一个例子就是。

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

    Why did the designers decide to force a method to specify all uncaught checked exceptions that can be thrown within its scope? Any Exception that can be thrown by a method is part of the method's public programming interface. Those who call a method must know about the exceptions that a method can throw so that they can decide what to do about them. These exceptions are as much a part of that method's programming interface as its parameters and return value.

    The next question might be:"If it's so good to document a method's API, including the exceptions it can throw, why not specify runtime exceptions too?" 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.

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

    The checked exception classes named in the throws clause are part of the contract between the implementor and user of the method or constructor.

    IMHO的底线是,您可以捕获任何RuntimeException,但您不需要这样做,而且实际上,不需要实现来维护抛出的未检查异常,因为这些异常不是合同的一部分。


    1)否,数字格式异常是未选中的异常。即使你抓住了它(你不需要这么做),因为它是未经检查的。这是因为它是IllegalArgumentException的一个子类,是RuntimeException的一个子类。

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

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

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

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


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

    如。。文件操作

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

    如。。

    1
    2
    3
    4
    5
    6
    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)

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


    检查的异常在编译时由JVM及其与资源(文件/db/stream/socket等)相关的资源检查。检查异常的动机是,在编译时,如果资源不可用,应用程序应该定义一个替代行为来在catch/finally块中处理这个异常。

    未选中的异常是纯粹的编程错误、错误的计算、空数据,甚至业务逻辑中的失败都可能导致运行时异常。在代码中处理/捕获未检查的异常是非常好的。

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


    运行时异常运行时异常称为未选中的异常。所有其他例外它们不是从java.lang.RuntimeException派生的。

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

    Java API抛出的许多方法检查异常,因此您将经常编写异常处理程序来应对由未写入的方法生成的异常。


    我最喜欢的描述未检查和检查异常之间的区别是由Java教程TRAIL文章提供的,"未经检查的例外-争议"(很抱歉在这篇文章中得到了初步的说明,但是,嘿,基本情况有时是最好的):

    Here's the bottom line guideline: 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

    "要抛出的异常类型"的核心是语义(在某种程度上),上面的引用提供了极好的指导原则(因此,我仍然被C摆脱检查过的异常的概念所震惊,特别是当Liskov为其有用性辩护时)。

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


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

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

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

    不,总是声明尽可能精确的异常,或者这样的列表。声明方法可以引发的异常是方法和调用方之间契约的一部分。抛出"FileNotFoundException"意味着文件名可能无效,并且找不到文件;调用方需要智能地处理。投掷Exception意味着"嘿,嘘,这是发生的。交易。"这是一个非常贫穷的API

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


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

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

    只要涉及到检查的异常,方法签名中就有一个throws CheckedException(CheckedException可以是任何检查的异常)。签名不会引发异常,引发异常是实现的一个方面。接口、方法签名、父类,所有这些都不应该依赖于它们的实现。这里使用的检查异常(实际上,您必须在方法签名中声明throws)将您的高级接口与这些接口的实现绑定在一起。

    我给你举个例子。

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

    1
    2
    3
    public interface IFoo {
        public void foo();
    }

    现在我们可以编写方法foo()的许多实现,如下所示

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

    福班很好。现在让我们第一次去酒吧

    1
    2
    3
    4
    5
    6
    7
    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();
        }
    }

    这个类栏无法编译。由于InterruptedException是一个选中的异常,因此必须捕获它(在方法foo()内使用try catch)或声明要抛出它(将throws InterruptedException添加到方法签名中)。因为我不想在这里捕获这个异常(我希望它向上传播,这样我就可以在其他地方正确地处理它),所以让我们更改签名。

    1
    2
    3
    4
    5
    6
    public class Bar implements IFoo {
        @Override
        public void foo() throws InterruptedException {
            throw new InterruptedException();
        }
    }

    这个类栏也不会编译!bar的方法foo()不会重写ifoo的方法foo(),因为它们的签名不同。我可以删除@override注释,但是我想针对接口ifoo编程,比如IFoo foo;,稍后再决定要使用哪个实现,比如foo = new Bar();。如果bar的方法foo()不重写i foo的方法foo,那么在执行foo.foo();时,它不会调用bar的foo()实现。

    为了使bar的public 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的其他实现造成严重破坏!

    这就是为什么选中的异常不好的一个重要原因。在帽子里。

    一种解决方案是捕获选中的异常,将其包装为未选中的异常并引发未选中的异常。


    简而言之,您的模块或以上模块在运行时应该处理的异常称为选中异常;其他的则称为未选中异常,它们要么是RuntimeException要么是Error

    在这个视频中,它解释了Java中的检查和未检查异常:https://www.youtube.com/watch?V= UE2POQLALAW


    为什么他们会让例外情况浮出水面?处理错误不是越早越好吗?为什么泡沫?

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

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


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

    阅读此处有关选中与未选中异常的更多信息http://learnjava.today/2015/11/checked-vs-unchecked-exceptions/


    只是要指出,如果您在代码中抛出一个检查过的异常,而catch高于几个级别,则需要在您和catch之间的每个方法的签名中声明该异常。因此,封装被破坏,因为throw路径中的所有函数都必须知道该异常的详细信息。


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

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

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

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

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

    要决定是否应该换行和重新换行,您应该再次考虑界面的用户必须立即处理异常条件是否有意义,或者异常太普遍,以至于您无法对其进行任何处理,它应该向上传播堆栈。包装的异常在表示为您定义的新接口的功能时是否有意义,或者它仅仅是可能发生在其他方法上的一袋错误条件的载体?如果是前者,它可能仍然是选中的异常,否则应该取消选中。

    您通常不应该计划"冒泡"异常(捕获并重新引发)。异常应该由调用方处理(在这种情况下是选中的),或者应该一直到高级处理程序(在这种情况下,如果不选中,最容易)。


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


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

    "尽管这是一个选中的异常,但它很少可恢复。大多数调用方只需将此异常包装在未选中的异常中,然后重新显示:"

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

    http://developer.android.com/reference/org/json/jsonexception.html网站


    检查异常:

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

    • 这些发生在编译时。

    • 如果处理不当,它们将给出编译时错误(非异常)。
    • 异常类的所有子类(runtimeexception除外)都被检查为异常。

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

    未选中的异常:

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

    • 这些发生在运行时。

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

    • runtimeexception和error的所有子类都是未选中的异常。

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


    必须检查所有异常。

  • 未选中的异常是不受限制的goto。无限制的哥特人被认为是一件坏事。

  • 未选中的异常会破坏封装。为了正确地处理它们,必须知道投掷者和接球者之间调用树中的所有函数,以避免出现错误。

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