Java 异常看似很简单,但是很多 Java 新手总是掌握不了,而很多做了多年的 Java 开发人员也拿捏不住。今天看到论坛里有人问关于 Java 异常处理的问题,就觉得这个话题还是值得一写。
Checked Exception 和 RuntimeException
Java 中的异常分为两种,一种是 Checked Exception,另一种是 Runtime Exception。前者的含义是虽然是异常情况,但是可以事先预料。比如用户请求的文件不存在,就会导致 FileNotFoundException。这个时候,你的系统可以通过提示用户其文件路径错误的方式来处理这个异常。后者往往代表着系统在正常运行过程中不应当出现的异常,并且系统无法自行修复的异常。例如,最常见的就是 NPE,出现这种异常往往代表系统设计有缺陷或代码存在 bug。
但并非所有的 RuntimeException 都不需要 try-catch 处理,也并非所有 CheckedException 都可以通过更多的代码让程序自行修复。例如,Integer.parseInt 等方法就会抛出 NumberFormatException,它是运行时异常。这个异常通常是用户输入的数据不符合数字的格式,这时最好是让用户重新输入,所以最好 try-catch 处理。
再例如,使用 JAXB 时必要要处理 JAXBException,这是一个 Checked Exception,出现这种异常往往是找不到 JAXB 实现(因为类加载等原因),JAXB Annotation 使用错误等。这些问题在一个正常的系统中是不应当出现的,而出现时通常便意味着 bug。所以这种异常通常只需 try-catch 后加 log 处理,而更好的方式是它是一个 Runtime Exception(但这基本是不可能的)
其实,JDK 及 Java EE 等混乱的 Checked Exception 和 RuntimeException 使用方式也颇让人诟病。所以在 Groovy 中(其它 JVM 语言不了解)索性去掉了 Checked Exception 这个概念以简化程序编写 (在 Groovy 中你不需要 try-catch 一个 Checked Exception)
如何处理异常
这个问题说起来也很简单。如果当前方法不知道如何处理某个异常,或者不应当由这一层程序处理时,就应当抛出异常,由上层调用者处理。如果某个异常(例如 JAXBException)各层都无法处理时,应当在它出现的时候就解决掉,方法通常是捕获异常,记录日志,或者封装成运行时异常抛出,但要确保上层会处理这个运行时异常(通常 Web 服务器等的至少会通过日志的方式处理)
虽然上述方法适合多数情况,但是对于某些异常,处理起来并不简单。例如,SQLException,你可能还需要根据 ErrorCode 来选择不同的处理方式(好在想 Spring、Hibernate 等框架帮我们做了这事)
自定义异常
出了要处理由 JDK 或第三方类库、框架抛出的异常意外,一个系统通常会有很多自定义的异常。这些异常往往代表业务异常的情况,通过自定义的异常使得代码和系统的行为更加清晰。自定义的异常可以是Checked Exception,也能是 Runtime Exception。前者通常用于通知上层调用者做适当的异常处理,后者通常用于由系统的某一层(通常是表现层或接口层)做统一的异常处理。
中断异常
线程中断异常(InterruptedException)是当前线程被中断的表现之一。遇到这个异常时,如果你不知道如何处理,你应当向上抛出。但如果碰巧你不能抛出(例如在 Runnable 中遇到),你就应当捕获 InterruptedException 之后再重新设置中断,方法是执行 Thread.currentThread().interrupt()。吞掉中断而不处理往往会导致在测试中不容易发现的问题。