异常机制,用于保障我们的程序更加健壮性(鲁棒性)。
程序员开发的程序,无论用户怎么操作,它都可以正常应对。
对于一个五子棋来说:
if(用户输入包含非数字、逗号之外的其他字符) {
// 进行错误处理
} else if (用户输入逗号) {
// 进行错误处理
} else if (用户输入坐标超出了范围) {
// 进行错误处理
} ......
{
// 最后才进行业务处理
}
这种机制有如下两个问题;
1、我们永远不可能“穷举”所有错误,并进行处理。
2、代码“臃肿”得难以忍受。
能不能把上面代码简化为:
if (一切正常) [
// 进行业务处理
} else if (出现任何各种错误) {
// 进行错误处理
}
Java的异常处理:
//尝试让它执行业务处理,如果可以执行完成,就代表了一切正常
try {
// 正常业务处理
} catch (异常1 e1) {
// 进行异常1处理
} catch (异常2 e2) {
// 进行异常2处理
}
不同的catch块负责对不同的异常进行处理。
异常的执行流程(P414 图10.1)
异常类的继承体系(P414)
对于一个异常,最多只有一个catch块能捕捉到该异常。
多个catch块捕捉异常,应该是先捕捉子类异常,再捕捉父类异常。
多异常捕捉(P417)
JDK 7 提供了新功能:多异常捕捉
多异常捕捉的异常形参默认有final修饰。
catch ( 类1 | 类2 | 类3 ex)
访问异常信息(P417)
当catch块捕捉到异常时,异常对象会作为catch块的参数被传入。
因此在catch块可通过该参数来访问实际的异常对象。
getMessage
返回异常的描述信息
public class AddTest {
public static void main(String[] args) {
try {
// args[0] 代表用户运行该程序传入的第1个字符串
Double d1 = Double.parseDouble(args[0]);
// args[0] 代表用户运行该程序传入的第2个字符串
Double d2 = Double.parseDouble(args[1]);
System.out.println(d1 + d2);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) {
// 多异常捕捉的异常形参默认有final修饰。
System.out.println("您必须输入2个数字来做加法运算!");
System.out.println(ex.getMessage());
}
}
}
printStackTrace
输出异常的跟踪栈信息
程序排错的方法
排错时,应该从异常发生的“第一个你自己开发的程序”开始排错。
异常处理的完整语法格式
try {
// 正常业务处理
} catch(异常 e1) {
// 进行异常1处理 0~N个
} finally {
}
try
不能“孤独”地存在——catch块和finally至少要有一个。
finally
——通常用于回收资源。
JVM会保证【finally块总会得到执行】,return语句也不能阻止。
无论程序是否抛出异常,甚至程序遇到return语句——都不能阻止finally的执行。
除非遇到退出虚拟机的代码,才能阻止finally的执行。
public class FinallyTest {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
int a = 20;
try {
System.out.println("执行try块");
return ++a; // 该return语句会执行,该方法本来应该返回
// 但finally总会获得执行的机会
/*
处理流程是:把return执行了,
在将要返回方法、但又未返回的时候,程序本来打算返回21
系统回去执行finally,当系统执行finally时,finally又遇到了return,
于是该finally的return把方法结束。此时返回22.
*/
} finally {
return ++a;
}
}
}
除非遇到退出虚拟机的代码,才能阻止finally的执行。
public class FinallyTest3 {
public static void main(String[] args) {
try {
System.out.println("执行try块");
// 退出虚拟机,就可以阻止finally的执行
// System.exit(1);
Runtime.getRuntime().exit(1);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
System.out.println("finally的执行");
}
}
}
异常处理可以嵌套
try {
} catch {
try {
}
} finally {
try {
}
}
JDK 7还提供一个自动关闭资源的try语句
try (
// 此处声明的资源,系统可以自动关闭它。
)
{
//
}
对于自动关闭资源的try语句,可以没有catch和finally——可以孤独地存在。
自动关闭资源的try语句,有两个注意点:
1、只有放在try后面的圆括号里的资源(不适合变量类型)才会被关闭。
2、能被自动关闭的资源必须实现Closeable或AutoCloseable接口。
import java.io.FileInputStream;
public class AutoClose {
public static void main(String[] args) throws Exception {
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\Program Files\\eclipse-java-2023-06-R-win32-x86_64\\workplace\\day\\src\\day12\\AutoClose.java");
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
}
finally {
try {
fis.close();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
上面的代码等价于下面的:
import java.io.FileInputStream;
public class AutoClose {
public static void main(String[] args) throws Exception {
try (
FileInputStream fis = new FileInputStream("D:\\Program Files\\eclipse-java-2023-06-R-win32-x86_64\\workplace\\day\\src\\day12\\AutoClose.java");
)
{
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
}
// 自动关闭资源的try,相当于在最后有一个隐藏的finally
}
}
异常分类
checked异常:普通的、不是RuntimeExcepiton子类的异常,都是checked异常。
runtime异常:RuntimeException的子类都是runtime异常。
【除非你直接,或间接继承了RuntimeException】
checked异常
Java设计初衷:没有完善的错误处理的代码,不会得到执行的机会。
Java设计了一种“checked”异常——编译器会强制检查。
编译器要求程序员:1、要么处理该异常。
2、要么声明抛出该异常。
runtime异常
程序员你想捕捉,就捕捉。
如果你现在不想处理,编译器不会进行强制检查。
checkded异常可以更好地保证【程序员一定会对异常进行处理】,
但checked也会给编程带来繁琐——要么用throws声明抛出,要么显式用catch来捕捉。
有名的Runtime异常
- NullPointerExcepiton
- ArrayIndexOutOfBoundsException
- ClassCastExcepiton
- ArithemethicExcepiton 比如整数除以0
- NumberFormatExcepiton
- IllegalArgumentException
抛出异常可能的两种情况
1、程序不能成功地执行完成,由系统自动生成异常对象,并抛出。
2、程序出现了与业务规则不符的情况,由程序员来创建异常对象,并通过throw关键字抛出。
即使是程序员通过throw抛出的异常,如果是checked异常,JDK同样会强制检查,要求程序员要么用try catch显式捕捉,要么用throws声明抛出。
Exception不属于Runtime异常,属于Checked异常。
Throwable也可以捕获Excepiton异常,因为Throwable包括了异常和错误,是最大的。
用throws声明抛出异常
该方法已经注意到了这几个异常,但目前该方法无法、不想处理该异常。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExcepitonType {
public static void main(String[] args) {
int b = 40 / 0; //这条代码肯定会出异常,但编译时可以通过
try {
FileInputStream fis = new FileInputStream("ExceptionType.java");
} catch (FileNotFoundException ex) {
System.out.println("修复、处理异常");
ex.printStackTrace();
}
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExcepitonType {
// main方法声明已经注意到可能发生FileNotFoundException异常
// 但main方法目前不想、不知道怎么处理该异常
public static void main(String[] args) throws FileNotFoundException {
int b = 40 / 0; //这条代码肯定会出异常,但编译时可以通过
FileInputStream fis = new FileInputStream("ExceptionType.java");
}
}
方法重写
两同(方法名相同、形参列表相同)
两小(⼦类⽅法的返回类型⼩于等于⽗类⽅法的返回类型、⼦类抛出的异常⼩于等于⽗类抛出的异常)
一大(⼦类⽅法的访问权限要⼤于等于⽗类⽅法的访问权限)
其中一小,就包括用throws声明抛出的异常必须更小。
【注意】子类方法不声明抛出异常,肯定是可以的。
throws与throw的区别
1、throws只能在方法签名中用。
throws后可以紧跟多个异常类的类名,多个类名之间用逗号(,)隔开。
2、throws就是一条普通的语句。
throws后紧跟一个异常实例。
catch与throw结合使用
有一种场景:A方法调用了B方法,当B方法出现了异常,而且B方法自己捕捉了异常。
此时A方法就不知道被调的B方法曾经发生过异常。
但有些时候,虽然B自己捕捉了异常,但A依然需要知道被调用方法曾经发生的异常。
此时的做法2,可以是先用catch来捕捉原有的异常,再用throw抛出 一个自定义异常。
专业说法叫做“异常转移”。
自定义异常
如果你想自定义runtime异常,继承RuntimeExcepiton。
如果你想自定义checkde异常,继承Excepiton。