Java中的已检查异常和未检查异常

24

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

  1. checked异常被认为是在编译时查找异常情况。不同来源提供的例子如数据库连接、文件处理等都是其中的一些,而unchecked异常则被认为是在程序员的错误方面查找异常情况,例如数组索引超出范围等。

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

2 其次,RunTimeException本身是unchecked,如何成为checkedException的子类?这意味着什么?

我在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);
    }
}

已检查异常并非用于检测编译时问题。这是编译器错误的作用。 - erickson
8个回答

31

如果使用CheckedException则需要由调用者处理,而Unchecked exception则不需要。

因此,在设计应用程序时,应考虑管理的异常情况类型。

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

或者,对于可以恢复的异常情况:假设您有一个负载均衡器,并且希望通知调用者其中“n”台服务器之一已关闭,因此调用者必须重新路由消息到另一台服务器来恢复事件;这应该是一个Checked Exception,因为关键是调用者(客户端)尝试恢复错误,而不仅仅让错误中断程序流程。

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

出于这些原因,JRE中也有一些Unchecked exceptions:OutOfMemoryError(无法恢复),NullPointerException(需要修复的bug),ArrayIndexOutOfBoundsException(另一个bug示例)等。

我个人认为SQLException也应该是Unchecked,因为它表示程序中的错误或数据库连接问题。但是有很多例外情况,您真的不知道如何处理(RemoteException)。

处理异常的最佳方式是:如果您能够恢复或管理异常,请处理它。否则,让异常继续传递;其他人将需要处理。如果您是最后一个“其他人”,并且不知道如何处理异常,只需显示它(在日志中记录或在用户界面上显示)。


很好。只有几个问题,如果存在无法恢复的错误,JVM 无论如何都会终止它。那么引发运行时异常的意义是什么?只是为了提供适当的状态消息吗?我认为异常可以让您捕获错误,进行一些修正工作,然后让程序正常恢复。其次,您如何从已检查的异常中“恢复”?您所说的“恢复”具体指什么? - SexyBeast
2
不完全是这样。如果文件系统崩溃,您的JVM可能仍然运行。我认为即使出现OutOfMemoryError,JVM也不会停止:有一些线程不需要额外的内存,因此它们可以继续运行,或者某些引用稍后可能会被释放,程序仍然可以正常工作。引发运行时异常意味着说“通常情况下,您无法处理此异常,但如果您愿意,可以随意处理”。例如,如果我设计了一台特殊的计算机,能够将新的RAM芯片安装到机器中,我也可以处理OutOfMemoryException! :) - Luigi R. Viggiano
通过“恢复”,我指的是这样一种情况:您调用一个方法,该方法通过套接字向远程计算机发送一些消息。如果连接暂时中断,我可以尝试再次尝试几次以恢复连接,然后放弃。您可以选择决定是最好让用户手动重试还是在软件中实现此“恢复”逻辑。如果您正在处理可以手动重试的用户,则“不恢复”的策略可能是最佳选择。但是,如果您正在实现无需用户交互的服务器,则必须考虑是否需要恢复临时网络故障。 - Luigi R. Viggiano
好的,但在这种情况下,我可以轻松地将恢复代码放在catch块中,为什么需要一个throws子句呢? - SexyBeast
1
throws子句是为了强制开发人员考虑需要处理的异常情况。它是一个“提示”。如果我创建了一个抛出未经检查的异常的验证框架,那么程序员可能会忘记捕获某些验证异常...从而导致错误。检查异常强制程序员执行以下两个操作之一:捕获并处理异常,或声明throws以让异常传递。这只是一个“提示”,以避免开发人员忘记处理需要处理的某些情况。未经检查的异常不需要使用throws子句。 - Luigi R. Viggiano
显示剩余2条评论

9
  1. throws子句中,您不需要声明未经检查的异常;但是您必须声明已检查的异常;
  2. RuntimeExceptionError以及它们的所有子类(IllegalArgumentExceptionStackOverflowError等)都是未经检查的异常;RuntimeException是未经检查的事实,与其他Throwable子类不同,这是有意设计的;
  3. 不存在所谓的"编译时异常"。

更一般地说,人们认为在JVM错误或程序员错误的情况下抛出未经检查的异常。一个著名的这样的异常是NullPointerException,通常缩写为NPE,它是RuntimeException的子类,因此未经检查。

未经检查的异常和已检查的异常之间另一个非常重要的区别是,在try-catch块中,如果您想捕获未经检查的异常,您必须明确地捕获它们

最后注意:如果您有异常类E1E2,并且E2扩展了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();
    }
}

我希望你能理解得更清楚些...

7
  1. 不是的。所有的异常都会在运行时发生。被检查的异常是强制调用者处理或声明的异常。它们通常旨在报告可恢复错误,这些错误不是由程序员错误引起的(例如文件不存在或网络连接问题)。运行时异常通常旨在报告不可恢复的错误。它们不会强制调用者处理或声明它们。许多运行时异常确实表示编程错误(例如 NullPointerException)。

  2. 因为JLS定义了未检查的异常:即扩展RuntimeException的异常或子类,而RuntimeException本身是Exception的子类。使用单一继承根允许在单个catch子句中处理每种可能的异常。

关于您的示例:是的,throws子句是强制性的,因为IOException是一个检查到的异常,而方法内部的代码有可能抛出其中一个异常。


1
所有的异常都发生在运行时。这是我的答案的第一句话。没有编译时异常。那不存在。throws表示一个方法在被调用时可能会抛出异常。try/catch捕获由被调用方法抛出的异常。throws就像一个“危险!地面湿滑”的标志牌。try/catch就像“哇,地面很滑,我会小心处理它”的感觉。 - JB Nizet
1
我不知道如何找到比那更简单的例子。如果你是地面清洁工,并想警告其他人地面很滑,你不会通过迈非常小的步来做到这一点。你会在地面上放置一个“警告!”面板。这就是 throws 的作用。如果你是一个行走在地面上的人,“危险”面板会让你意识到地面很滑,你应该迈非常小的步来避免摔倒。这就是 try/catch 的作用:它通过执行特定的操作来处理异常情况。 - JB Nizet
但即使使用普通的 try-catch,调用者也捕获可能出现的异常。因此,他绝对不会忽略异常情况(否则为什么要使用 try-catch?),并且肯定被迫考虑和处理它。我知道我可能听起来有点愚蠢,请您帮助我理解! - SexyBeast
如果你没有prompt()方法的源代码(因为它是库的一部分),如果该方法的javadoc根本没有提到IOException,而且编译器也没有告诉你“嘿,你正在调用prompt(),所以你必须处理IOException,因为有可能prompt会抛出一个IOException”,那么你怎么知道这样的异常情况可能发生,你最好处理它呢?你不会知道它的存在,所以你不会在对prompt()的调用周围使用try/catch,当异常情况发生时,代码将会惨败。 - JB Nizet
1
哪两个方法?只有一个方法。IOException 必须在抛出它的方法(prompt())的 throws 子句中声明。调用者(main())必须捕获它,或者也必须在其 throws 子句中声明它。你别无选择。如果你不这样做,编译器将拒绝编译你的代码。 - JB Nizet
显示剩余6条评论

2
编译器只会确保一个方法在未声明异常的情况下不会抛出已检查的异常。人们普遍认为编译器应该对那些程序员无法控制的异常进行此类检查,例如您引用的数据库连接、文件丢失等例子。未经检查的异常“不应该发生”,因此编译器不强制您声明它们。
至于模拟 IOException 或其他任何异常,这是微不足道的:
throw new IOException();

在你的例子中,prompt方法可能会抛出IOException异常,因此需要声明它。这与您在调用该方法时如何处理异常无关。 RuntimeExceptionException的子类,方便使用一个catch Exception语句捕获所有异常。这可以设计得不同;Java的异常类层次结构很混乱。

文件处理或数据库连接是在运行时完成的,如何在编译时检查它们? - SexyBeast
编译器只会确保一个方法在未声明异常时无法抛出已检测异常。 - Marko Topolnik

1

如果您在此处不放置throws子句,则会出现此错误

ThrowsDemo.java:5: 未报告的异常java.io.IOException; 必须捕获或声明为抛出 return (char) System.in.read();

因此,必须使用throws子句。


1

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

Unchecked Exception
   -- NullPointerException
   -- ArrayIndexOutofBound
   -- IllegalArgument Exception
   -- ClassCastException
   -- IllegalStateException
   -- ConcurrentModificationException

Checked Exception Five Example
   -- FileNotFoundException
   -- ParseException
   -- ClassNotFoundException
   -- CloneNotSupportException
   -- SQLException

0

了解更多细节

当客户端代码可以基于异常信息采取有用的恢复措施时,请使用已检查异常。当客户端代码无法执行任何操作时,请使用未检查异常。例如,如果客户端代码可以从 SQLException 中恢复,请将其转换为另一个已检查异常,如果客户端代码无法处理它,则将其转换为未检查异常(即 RuntimeException)。


0

已检查和未检查的异常

有两种类型的异常:已检查的异常和未检查的异常。已检查的异常在编译时进行检查,而未检查的异常在运行时进行检查。

请阅读此文章以获得清晰的理解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接