前言
Java 使用异常来表示错误,并通过 try … catch 捕获异常;
Java的异常是class,并且从 Throwable
继承;
Throwable有两个体系:Error
和 Exception
。
Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误;
Exception又分为两大类:
- RuntimeException以及它的子类;
- 非RuntimeException(包括IOException、ReflectiveOperationException等等)
RuntimeException无需强制捕获,非RuntimeException(Checked Exception)需强制捕获,或者用throws声明;
不推荐捕获了异常但不进行任何处理。
捕获异常
在Java中,凡是可能抛出异常的语句,都可以用try … catch捕获。把可能发生异常的语句放在 try { … } 中,然后使用catch捕获对应的Exception及其子类。
存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。例如:
1 | public static void main(String[] args) { |
对于上面的代码,UnsupportedEncodingException异常是永远捕获不到的,因为它是IOException的子类。当抛出UnsupportedEncodingException异常时,会被catch (IOException e) { … }捕获并执行。
Java的try … catch机制还提供了finally语句,finally语句块保证有无错误都会执行:
1 | public static void main(String[] args) { |
注意finally有几个特点:
- finally语句不是必须的,可写可不写;
- finally总是最后执行。
使用try … catch … finally时:
- 多个catch语句的匹配顺序非常重要,子类必须放在前面;
- finally语句保证了有无异常都会执行,它是可选的;
- 一个catch语句也可以匹配多个非继承关系的异常。
主动抛出异常
如何抛出异常?参考Integer.parseInt()方法,抛出异常分两步:
- 创建某个Exception的实例;
- 用throw语句抛出。
1 | void process2(String s) { |
调用printStackTrace()可以打印异常的传播栈,对于调试非常有用;
捕获异常并再次抛出新的异常时,应该持有原始异常信息;
通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
自定义异常
在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。
一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。
BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:
1 | public class BaseException extends RuntimeException { |
其他业务类型的异常就可以从BaseException派生:
1 | public class UserNotFoundException extends BaseException { |
自定义的BaseException应该提供多个构造方法:
1 | public class BaseException extends RuntimeException { |
上述构造方法实际上都是原样照抄RuntimeException。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。
使用断言
断言(Assertion)是一种调试程序的方式。在Java中,使用assert关键字来实现断言。
我们先看一个例子:
1 | public static void main(String[] args) { |
语句 assert x >= 0
; 即为断言,断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError。
使用assert语句时,还可以添加一个可选的断言消息:
1 | assert x >= 0 : "x must >= 0"; |
这样,断言失败的时候,AssertionError会带上消息x must >= 0,更加便于调试。
ava断言的特点是:断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。
对于可恢复的程序错误,不应该使用断言。例如:
1 | void sort(int[] arr) { |
应该抛出异常并在上层捕获:
1 | void sort(int[] arr) { |
JVM默认关闭断言指令,即遇到assert语句就自动忽略了,不执行。
要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)参数启用断言。所以,上述程序必须在命令行下运行才有效果:
$ java -ea Main.java