写在前面: 查阅「Java官方手册」(Java 11 官方API)和学习「Java源码」是学习这门语言的必经之路。
1 异常的基本概念
Java中的「异常」指程序执行中发生的不正常情况,常见的异常如下图所示:
以上的异常类都存在于java.lang
包中,以上类的具体含义如下:
- Throwable类:Java中Error和Exception的超类
- Error类:描述Java虚拟机无法解决的严重错误,如,JVM挂掉的情况
- Exception类:描述因编程错误或其他因素导致的轻微错误,可通过修改程序解决,如,以8为下标访问长度为7的数据,会发生
ArrayIndexOutOfBoundsException
异常 - IOException类和其他异常:检测性异常,能够在编译阶段能被编译器识别的异常
- RuntimeException类:非检测性异常,在运行阶段才能被识别的异常
- 虚线框中的非检测性异常类:Java初学者常出现的5种异常
2 异常捕获
异常的捕获方法为:try{} catch {} ... finally{}
,具体写法参照注意点4中的代码。
注意点:
- 编写多个catch分支时,需要把小类型放在大类型前面
- catch (Exception e) {…} 可以直接捕获所有异常
- finally中的代码块是必执行的,比如打开文件后,可以把关闭文件的代码放在这个部分
- 虽然return是结束程序并返回,但是finally在结束程序前必须执行
针对注意点4,举个例子:
阅读下述代码,请问打印的 i 等于几?
public class ExceptionCatchTest {
public static int finallyReturnExample() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("i = " + (100 / i));
}
return 0;
} catch (ArithmeticException e) {
e.printStackTrace();
return 1;
} finally {
return 2;
}
}
public static void main(String[] args) {
int i = ExceptionCatchTest.finallyReturnExample();
System.out.println("i = " + i);
}
}
注:不妨执行下上述代码,体会 i 是否等于你心里想的那个数~
3 异常的抛出
异常的抛出:有些异常在特殊情况无法或不方便处理,这时,可以将该异常转移给该方法的调用者。
语法格式如下:
访问权限 返回值类型 方法名称(形参列表) throws 异常类型1, 异常类型2, ... {
方法体;
}
// 例如:
public void show() throws ArithmetictException {}
注意点:
- 子类重写父类的方法:不能抛出「更大的异常、平级但名称不一样的异常」,可以抛出「一样的异常、更小的异常」以及「不抛出异常」
- 建议不要在main方法中抛出异常,JVM的负担很重,到了main这一层采用就地解决(异常捕获)的方式
- 父类被重写的方法没有抛出异常、子类中重写的方法只能进行异常的捕获处理
- 若一个方法内部以递进的方式调用了很多其他的方法,建议在方法内使用抛出的方法处理到最后一层进行捕获处理
针对注意点4,举个例子:
public class InnerThrowsTest {
public static void show() throws IOException {
// FileInputStream产生异常,直接抛出
FileInputStream fis = new FileInputStream("./a.txt");
fis.close();
}
public static void test1() throws IOException {
show(); // test1()调用show(), 异常抛出给test1()
}
public static void test2() throws IOException {
test1(); // test2()调用test1(), 异常抛出给test2()
}
public static void main(String[] args) /*throws IOException*/ {
// 到main层就地处理,不再抛出
try {
test2();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4 自定义异常
当Java官方没提供「自己的项目」所需的针对性异常,则需要自定义描述这类异常。
编写流程:
- 自定义@@@Exception异常继承Exception类或其子类
- 提供序列化版本号
- 提供两个版本的构造方法:
- 无参构造方法
- 以字符串为参数的有参构造方法
异常的产生:throw new 异常类型(实参);
举个例子:
若要开发一个学生管理系统,存储学生信息时会涉及到如学号、年龄等需要进行异常值判定的成员变量,这里以学生年龄的为例,我们需要对不合理的年龄时产生异常对象并抛出,具体如下:
// 参考Exception类的源码进行设计自定义异常
public class AgeException extends Exception {
static final long serialVersionUID = 7818375828146090155L; // 序列化的版本号
public AgeException() {
} // 无参构造
public AgeException(String message) {
super(message);
} // 有参构造,以字符串为参数
}
// 为了体现如何使用自定义异常,仅展示一个年龄成员变量的封装
public class Student {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if(age >= 0 && age <= 150) {
this.age = age;
} else {
try {
throw new AgeException("输入的年龄不符合要求!");
} catch (AgeException e) {
e.printStackTrace();
}
}
}
}
异常的处理总原则:
- 在开发中采用
if
条件判断尽量避免异常; - 避免不了则进行异常的捕获;
- 捕获不了则进行异常的抛出;
- 另外,可采用自定义异常来对针对性异常进行处理。