【Java异常处理】6-2 抛出和捕获异常
§6-2 抛出和捕获异常
本节内容将介绍如何处理异常。
6-2.1 处理异常的方式
在 Java 中,处理异常的方式有以下三种:
- JVM 默认处理方式:将异常名称、异常原因、一场出现的代码行等信息打印到控制台上,并结束程序;
- 产生异常的方法内部自行处理;
- 交由调用者处理,向调用者抛出异常;
6-2.2 使用 try, catch, finally 环绕自行处理异常
若使用 JVM 的默认异常处理方式,程序会在异常代码处终止运行。在可能抛出异常的语句使用 try-catch 语句环绕,可让程序继续运行。若调用一个可能会抛出异常的方法,在调用语句使用该语句块环绕也可以防止程序终止运行。
语法:
try {
//保护代码块,监控区域
} catch (ExceptionType e1) {
//捕获区域
} catch (ExceptionType e2) {
//捕获区域,可进行多重捕获
} finally {
//无论是否发生异常,finally代码块中的代码都会被执行,可选
//可以运行善后性质的语句,例如用于关闭流
}
注意:
try后面不能既没有catch也没有finally;catch不能独立于try而存在;try,catch和finally间不能夹有其他代码。
示例:在主方法中:
public static void main(String[] args) {
int a = 1;
int b = 0;
//使用try catch环绕,在IDEA中可选中代码,按下组合键Ctrl + Alt + T选择,自动生成代码块
try {
//监控区域
System.out.println(a/b);
} catch (ArithmeticException e) {
//捕获区域
System.out.println("被除数不能为0");
e.printStackTrace(); //打印异常信息
System.exit(1); //终止程序,通常以非零数表示非正常终止
} finally {
System.out.println("Finally");
}
}
其中 catch 后的参数为所捕获的异常类型,若明确已知抛出的异常类型,可以填上具体的异常类型,否则,可填 Exception(捕获异常)、Error(捕获错误)、Throwable(前面二者的超类),随后跟着一个变量,在这一例子中,变量为 e。
运行,得到
被除数不能为0
java.lang.ArithmeticException: / by zero
at com.oop.exception.Test.main(Test.java:11)
进程已结束,退出代码1
至此,程序非正常终止。
多重捕获:
但是,程序运行时产生的往往可能不止一种错误,catch 语句允许类似于 if else 结构一样,进行多重捕获。其捕获的顺序是自上而下,直至遇到第一个匹配的异常或者通过所有的捕获块。因此,建议将高级异常写在后面,防止被提前捕获,并捕获可能遗漏的异常。
public static void main(String[] args) {
int[] arr = {}; //长度为零的数组
try {
System.out.println(getMax(arr));
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组长度为零。")
}
}
public static int getMax(int[] arr) {
//throw 关键字在下文介绍,此处可先简单理解为产生异常并结束运行该方法
if (arr == null) {
//若数组为空
throw new NullPointerException();
}
if (arr.length == 0) {
//数组长度为零
throw new ArrayIndexOutOfBoundsException(0);
}
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max)
max = arr[i];
}
return max;
}
运行得到
被除数不能为0
进程已结束,退出代码0
另外,也可以使用一个 catch 块捕获多个异常,只需要在不同的异常间使用 | 分隔开即可,只要 try 语句块中生成的异常对象能够被其中一个异常捕获,则该 catch 块就会执行。例如:
int[] arr = {1,2,3,4,5};
try {
System.out.println(arr[5]);
System.out.println(2/0); //由于上一句已经抛出异常,这一句(及其后的)会被短路
} catch (ArrayIndexOutOfBoundsException e | ArithmeticException e) {
System.out.println("抛出异常");
}
运行过程:
- 若
try代码块中的代码没有产生问题,则其中代码全部执行,catch块不会执行(有且仅有抛出异常且被捕获才会执行); - 当
try代码块中的代码出现了异常,会创建对应异常的对象,try出现异常语句之后的所有语句将会逃过; - 如果
try可能会出现多个问题,则应当用多个catch多重捕获; - 随后,该异常对象会以顺序结构逐个与
catch中的异常做对比,看看其括号中的变量能否接收这个异常对象; - 若找不到可以接收的异常,则会将异常交由 JVM 默认处理;
- 若能接收,则该异常被捕获,执行该捕获块中的代码,其后的捕获块将会被忽略;
- 捕获的异常若具有继承关系,父类异常一定要位于后面(次序靠后);
- 当所有代码执行完毕,执行
finally块中代码(如果有的话),随后执行try-catch语句块后的代码;
6-2.3 异常中的常见方法
整个异常处理框架最顶级的超类是 Throwable,该类提供了一些异常的处理方法。
常用方法:
| 方法 | 描述 |
|---|---|
String getMessage() |
返回此 Throwable 的细节信息 |
String toString() |
返回此 Throwable 的简要描述 |
void printStackTrace() |
将此 Throwable 及其回溯打印到标准错误流 |
注意:
- "回溯" 英文原文为 backtrace,实际上呈现的是方法调用关系的一个功能;
- 一般而言,不建议处理异常时,仅仅简单地将错误信息打印到控制台上;
getMessage()返回的是异常的细节信息,例如索引越界Index x out of bounds for length l;toString()返回的是异常名称 + 细节信息;printStackTrace()用红色的字体在控制台中输出错误信息,该方法更常用,含有更多信息。
6-2.4 throw 和 throws 抛出异常
try-catch 一般用于方法调用处,防止程序终止运行。throw 和 throws 关键字都是用于方法,效果都是向方法调用者抛出异常,但二者具有一些区别。
**throw **:throw 关键字写在方法内,用于结束方法,并手动抛出异常对象,交给调用者。抛出对象后,方法下面的代码将不再执行。
public void test(int a, int b) {
//相除
if (b == 0) {
throw new ArithmeticException(); //在方法中主动抛出异常
}
}
在主方法中调用该方法,得到
Exception in thread "main" java.lang.ArithmeticException
at com.oop.exception.Test.test(Test.java:29)
at com.oop.exception.Test.main(Test.java:23)
可以在方法内在 throw 字段使用 try-catch 环绕处理异常。
throws:写在方法签名处,用于声明异常,告诉调用者,调用本方法可能会抛出什么异常。
public void test(int a, int b) throws ArimeticException {
System.out.println(a/b);
}
此时,在调用该方法时,需要用 try-catch 环绕:
try {
test(1,0);
} catch (Exception e) {
System.out.println("被除数不能为0");
}
运行,得到
被除数不能为0
进程已结束,退出代码0
一个方法也可以抛出多个异常,只需要在不同的异常之间用 , 分隔即可。
这种方法可以向调用者告知底层执行情况,让调用者处理异常。
注意:throws 位于方法的签名,若为编译时异常,则必须要写,运行时异常可以不写。
回顾:在面向对象的章节中,曾有讲过实现 Cloneable 接口重写 clone() 方法的复制对象方法。这时,重写方法必须要向外抛出 CloneNotSupportedException 异常。
6-2.5 assert 断言关键字
断言是一种在开发中常用的技术手段,用于判断程序执行所需要的条件是否满足。
用法:
assert expression : "Error message(Optional, but recommended)";
执行时,会首先计算布尔表达式 expression 的值,若为 true,程序继续正常执行。若为 false,则抛出错误 AssertionEror,并附带所给的 Error message,程序中止执行。
所抛出的 AssertionError 是错误,该错误不能够被 try-catch 语句块捕获。
assert 关键字常用于开发和测试环境中的预警处理,例如用于检查条件是否满足、捕获非法情况(并非错误情况)、确认方法参数。这样做可在开发和测试阶段定位和检查程序潜在的错误,提高程序健壮性、代码可读性和可维护性。但在实际生产工作环境中,应当极力避免断言失败而导致程序的异常退出或中断。因此,JVM 在默认情况下禁用断言。
若要启用断言,可在启动程序时在命令行中添加参数 -enableassertions 或其简称 -ea 启用断言。若要禁用断言,可添加参数 -disableassertions 或其简称 -da 以禁用断言。
使用建议:
- 仅在开发和测试环境中启用断言以检查潜在错误,使用断言时应当提供清晰、易读的错误信息;
- 不要滥用断言,断言会在条件不满足时强制抛出错误并终止程序;
- 每一句断言只检查一个条件,这样更直观地判断失败条件;
- 不要在断言中使用改变环境的语句,一旦条件不满足,改变环境的语句可能不会执行;
- 断言应当与后面的语句空出一行,形成逻辑和视觉上的一致感;
工作流程:
- 编译器阶段:编译时,编译器会通过检查
assert语句的语法和语义生成相应的字节码指令。若表达式为true,则生成一条空指令nop;否则抛出错误AssertionError; - 运行时阶段:当程序执行到
assert语句时,若条件表达式为true,则跳过该语句,程序继续正常执行;否则,生成一条带有给定错误信息的AssertionError并抛出。该错误是一个严重错误,且无法用try-catch捕获,程序会终止执行。
浙公网安备 33010602011771号