【问题标题】:Checked vs Unchecked Exceptions in JavaJava 中的已检查异常与未检查异常
【发布时间】:2012-12-23 14:28:24
【问题描述】:

我在理解 Java 中 checkedunchecked 异常之间的区别时遇到了一些问题。

  1. 首先,checked 异常应该在编译时寻找异常。不同来源提供的示例引用了数据库连接、文件处理作为其中的一些,而unchecked 异常应该查找程序员方面的错误,例如超出数组范围的索引等。

不应该反过来吗?我的意思是,数据库连接是在运行时完成的,对吧?文件处理也是如此。您在编译期间不打开文件句柄,那么为什么在编译期间寻找可能的错误呢?另一方面,对超出范围的数组进行索引已经在程序中完成,可以在编译时检查(如果异常索引是用户在运行时提供的,那么它可以是运行时问题)。我在这里错过了什么?

2 其次,RunTimeException,本身就是unchecked,怎么能子类Exception,也就是checked?这意味着什么?

我在 Herbert Schildt 的书中找到了一个例子,解释了checked异常的用法:

class ThrowsDemo {
   public static char prompt(String str)
      throws java.io.IOException {
  System.out.print(str + ": ");
  return (char) System.in.read();
  }
  public static void main(String args[]) {
    char ch;
    try {
      ch = prompt("Enter a letter");
    }
    catch(java.io.IOException exc) {
     System.out.println("I/O exception occurred.");
     ch = 'X';
    }
    System.out.println("You pressed " + ch);
    }
}

这里需要throws 子句吗?为什么我不能正常使用这样的try-catch 语句(抱歉我不知道如何模拟IO Exception,所以无法自己检查!):

class ThrowsDemo {
   public static char prompt(String str)  {
     System.out.print(str + ": ");
     return (char) System.in.read();
  }
  public static void main(String args[]) {
    char ch;
    try {
      ch = prompt("Enter a letter");
    }
    catch(java.io.IOException exc) {
     System.out.println("I/O exception occurred.");
     ch = 'X';
    }
    System.out.println("You pressed " + ch);
    }
}

【问题讨论】:

标签: java exception


【解决方案1】:

CheckedException 需要由调用者处理,Unchecked 异常不需要。

因此,当您设计应用程序时,您应该考虑到您正在处理什么样的异常情况。

例如,如果您设计了一个验证方法来检查某些用户输入的有效性,那么您知道调用者必须检查验证异常并以美观的方式向用户显示错误。这应该是一个检查异常。

或者,对于那些可以恢复的异常情况:假设您有一个负载平衡器,并且您想通知调用者“n”台服务器之一已关闭,因此调用者必须恢复事件,将消息重新路由到另一台服务器;这应该是一个检查异常,因为调用者(客户端)尝试恢复错误至关重要,不要让错误破坏程序流程。

相反,有许多情况不应该发生,和/或应该破坏程序。例如,编程错误(如除零、空指针异常)、错误使用 API(IllegalStateException、OperationNotSupportedException)、硬件崩溃或一些不可恢复的小情况(失去与服务器的连接),或世界末日:-);在这些情况下,正常的处理是让异常到达代码的最外层,向用户显示发生了不可预知的错误并且应用程序无法继续执行。这是一种致命的情况,因此您唯一能做的就是将其打印到日志中或在用户界面中将其显示给用户。在这种情况下,捕获异常是错误的,因为捕获异常后需要手动停止程序,以免造成更大的损失;所以最好让某种异常“打击粉丝”:)

由于这些原因,JRE 中也有一些未检查的异常:OutOfMemoryError(不可恢复)、NullPointerException(这是一个需要修复的错误)、ArrayIndexOutOfBoundsException(另一个错误示例)等等。

我个人认为 SQLException 也应该不选中,因为它表示程序中的错误,或者与数据库的连接问题。但是有很多例子你会遇到你真的不知道如何管理的异常 (RemoteException)。

处理异常的最佳方法是:如果您可以恢复或管理异常,请处理它。否则让异常过去;其他人将需要处理。如果您是最后一个“其他人”并且您不知道如何处理异常,只需将其显示(记录或显示在 UI 中)。

【讨论】:

  • 酷。只是几个问题,如果出现不可恢复的错误,JVM 无论如何都会终止它。那么引发运行时异常有什么意义呢?只给出适当的状态信息?我的印象是异常可以让你捕获错误,做一些修改工作并让程序正常恢复。其次,您如何从已检查的异常中“恢复”?你所说的“恢复”到底是什么意思?
  • 并非如此。如果文件系统发生崩溃,您的 JVM 可能仍会运行。我认为,如果出现 OutOfMemoryError,JVM 也不会停止:有些线程不需要额外的内存,因此它们可以继续运行,或者稍后可能会释放一些引用,并且程序可能仍然可以继续正常工作。引发运行时异常意味着说“这个异常,通常你不会/不能处理,但如果你愿意,请随意”。例如,如果我设计了一台能够将新 RAM 芯片安装到机器中的特殊计算机,我也可以处理 OutOfMemoryException! :)
  • “恢复”是指这样的场景:您调用一个方法,该方法通过套接字向远程计算机发送一些消息。如果连接暂时断开,我可能会在放弃之前再试几次恢复。您可以选择让用户手动重试还是在您的软件中实现此“恢复”逻辑。如果您正在与可以手动重试的用户打交道,那么“不恢复”策略可能是最好的。但是如果你正在实现一个没有用户交互的服务器,你必须考虑是否是恢复临时网络故障的情况
  • 好的,但是在这种情况下,我可以轻松地将恢复代码放在catch 块中,为什么需要throws 子句?
  • throws 子句是要求开发者考虑到有一个异常应该被管理的事实。是“纪念品”。如果我创建了一个抛出未经检查的异常的验证框架,那么程序员可能会忘记捕获一些验证异常......并且有一个错误。检查的异常迫使程序员做两件事之一:捕获异常并处理它,或者让异常通过声明抛出。这只是一个“纪念品”,以避免开发人员忘记处理一些需要处理的情况。未经检查的异常不需要throws 子句
【解决方案2】:
  1. 您不需要在throws 子句中声明未经检查的异常;但是您必须声明已检查的异常;
  2. RuntimeExceptionError,以及它们的所有子类(IllegalArgumentExceptionStackOverflowError 等),都是未经检查的异常;与其他 Throwable 子类不同,RuntimeException 未选中这一事实是设计使然;
  3. 没有“编译时异常”之类的东西。

更一般地说,如果发生 JVM 错误或程序员错误,则认为会引发未经检查的异常。一个著名的此类例外是 NullPointerException,通常缩写为 NPE,它是 RuntimeException 的子类,因此未选中。

未检查异常和已检查异常的另一个非常重要的区别是,在 try-catch 块中,如果要捕获未检查异常,必须显式捕获它们

最后说明:如果您有异常类 E1E2E2 扩展 E1,那么捕获和/或抛出 E1 也会捕获/抛出 E2。这代表已检查和未检查的异常。这对catch 块有影响:如果您确实区分捕获E2E1,您必须首先捕获E2

例如:

// IllegalArgumentException is unchecked, no need to declare it
public void illegal()
{
    throw new IllegalArgumentException("meh");
}

// IOException is a checked exception, it must be declared
public void ioerror()
    throws IOException
{
    throw new IOException("meh");
}

// Sample code using illegal(): if you want to catch IllegalArgumentException,
// you must do so explicitly. Not catching it is not considered an error
public void f()
{
    try {
        illegal();
    } catch (IllegalArgumentException e) { // Explicit catch!
        doSomething();
    }
}

我希望这能让事情更清楚......

【讨论】:

    【解决方案3】:
    1. 没有。所有异常都发生在运行时。已检查异常是强制调用者处理或声明它们的异常。它们通常用于指示可恢复的错误,这些错误不是由程序员错误引起的(例如文件不存在或网络连接问题)。运行时异常通常用于表示不可恢复的错误。他们不会强迫调用者处理或声明它们。其中许多确实表示编程错误(如 NullPointerException)。

    2. 因为这就是 JLS 定义未经检查的异常的方式:一个是或扩展 RuntimeException 的异常,它本身扩展了 Exception。使用单个继承根允许在单个 catch 子句中处理所有可能的异常。

    关于你的例子:是的,throws 子句是强制性的,因为 IOException 是一个检查异常,并且方法内的代码很容易抛出一个。

    【讨论】:

    • 我不明白,如何在编译期间检查文件句柄的有效性?为什么我不能用普通的try-catch 语句来做呢? throws 在这里实现了 try-catch 不能实现的什么?
    • 所有异常都发生在运行时。这是我回答的第一句话。没有编译时异常。那是不存在的。 throws 表示方法可以在调用时抛出异常。 try/catch 捕获被调用方法抛出的异常。 throws 就像一个“危险!湿滑的地面”面板。 try/catch 就像“哇,地很滑。我会照顾它的”。
    • 那为什么我不能总是用try-catch代替throws呢?
    • 我不知道如何找到比这更简单的示例。如果您是地面清洁工,并且想警告其他人地面湿滑,您不会通过非常小的步骤来做到这一点。您可以通过放置“警告!”来做到这一点。地面上的面板。这就是throws 所做的。如果你是一个在地上行走的人,“危险”面板会让你意识到地面很滑,你应该迈出很小的步子来避免跌倒。这就是try/catch 所做的:它通过执行特定的操作来处理异常情况。
    • 哪两种方法?只有一种方法。 IOException 必须在抛出它的方法的 throws 子句中声明 (prompt())。调用者 (main()) 必须捕获它,或者必须在其 throws 子句中声明它。你别无选择。如果你不这样做,编译器将拒绝编译你的代码。
    【解决方案4】:

    如果你不在这里放 throws 子句就会出现这个错误

    ThrowsDemo.java:5:未报告的异常 java.io.IOException;必须被抓住或 d 宣布被抛出 return (char) System.in.read();

    因此需要抛出子句。

    【讨论】:

      【解决方案5】:

      已检查异常和未检查异常的五个示例。

      Unchecked Exception
         -- NullPointerException
         -- ArrayIndexOutofBound
         -- IllegalArgument Exception
         -- ClassCastException
         -- IllegalStateException
         -- ConcurrentModificationException
      
      Checked Exception Five Example
         -- FileNotFoundException
         -- ParseException
         -- ClassNotFoundException
         -- CloneNotSupportException
         -- SQLException
      

      【讨论】:

        【解决方案6】:

        编译器仅确保方法在未声明的情况下不会抛出已检查异常。人们普遍认为,编译器应该对发生超出程序员控制范围的异常进行此类检查,例如您引用的示例(数据库连接、文件丢失等)。未经检查的异常“不应该发生”,因此编译器不会强制您声明它们。

        至于模拟IOException或任何其他,这是微不足道的:

        throw new IOException();
        

        在您的示例中,prompt 方法可能会抛出 IOException,这就是它需要声明它的原因。这与您在调用方法时如何处理异常无关。

        RuntimeExceptionException 的子类,以便使用一个catch Exception 子句捕获所有异常。这可以设计不同; Java 的异常类层次结构一团糟。

        【讨论】:

        • 文件处理或数据库连接是在运行时完成的,如何在编译时检查它们?
        • 编译器只确保一个方法如果没有声明它就不能抛出一个检查异常。
        【解决方案7】:

        已检查和未检查的异常

        有两种类型的异常:已检查的异常和未检查的异常。 已检查异常和未检查异常的主要区别在于,已检查异常在编译时检查,而未检查异常在运行时检查。

        请阅读此article 以获得清晰的想法。

        【讨论】:

          【解决方案8】:

          More Details

          当客户端代码可以根据异常中的信息采取一些有用的恢复操作时,使用检查异常。当客户端代码不能做任何事情时使用未经检查的异常。例如,如果客户端代码可以从中恢复,则将您的 SQLException 转换为另一个已检查的异常;如果客户端代码对此无能为力,则将您的 SQLException 转换为未经检查的(即 RuntimeException)异常。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-09-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多