目录
2. try...catch.. 捕获到异常之后代码的执行顺序?
3.1 如果 try...catch... 中没有出现异常,程序如何执行?
3.2 如果 try...catch... 中出现多个异常,程序会怎么执行?
3.3 如果 try... catch... 中遇到的异常没有被捕获,该怎么执行?
3.4 如果 try 中遇到了问题,那么下面的其他代码还会执行吗?
4. 什么时候使用 try...catch... ?什么时候使用 throws或 throw?
1. 异常处理的三种方法
上篇重点讲解了 Java 异常体系家族的分类,分析了运行时异常和编译时异常的由来,又举了几个例子。
那么本篇文章着重讲解 Java 异常的三种处理方式以及它们的使用细节。
在Java中,对于程序出现的异常,我们有以下三种解决方案。
1.1 JVM 默认处理异常
如果我们对异常不做任何处理的话,Java底层会使用JVM虚拟机默认的异常处理方式,当程序出现异常时,JVM虚拟机会把异常的名称,异常的信息以及异常出现的位置输出在控制台;
此外,一旦程序出现异常,JVM虚拟机就会立即停止程序,出现异常的代码下方的代码都不会执行。
如下代码,我简单写一个数组越界异常,使用JVM默认的异常处理方式,来看看它会怎么做
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
arr[3] = 3;
System.out.println("看看我会执行吗");
}
运行上述代码,可以在控制台得到如下结果
我们可以看到,System.out.println("看看我会执行吗")这一行代码并没有打印出来;
在控制台中,红色语句便是异常的描述
Exception in thread "main" 表示异常出现在在线程 main 中,也就是我们所编写的 main 方法;
java.lang.ArrayIndexOutOfBoundsException 表示我们出现的异常的名称,翻译过来就是数组越索引界异常;
at 是英语单词,意思是在;
cn.itcast.user.pojo.Test01.main 表示的是我们项目中的 包名 + 类名 + 方法名;
括号内蓝色字体 Test01.java:12 表示异常出现在Test01.java 类中的第十二行;
把整个异常信息综合起来意思就是说,"包名为 cn.itcast.user.pojo 类名为 Test01.java 的 main 方法的第十二行出现了 ArrayIndexOutOfBoundsException 数组索引越界异常";
还有一些情况,可能会出现多个异常,这种情况下,我们应该从下往上读,这里我就不展示了。
1.2 通过 try...catch...自己处理异常
如果我们不采用 JVM 默认的异常处理机制,我们就可以自己手动添加对异常的处理,而 try...catch... 就是方法之一,使用格式如下图所示
在 try 语句块中,我们需要将可能出现异常的代码写在括号内,然后在 catch 语句块中,我们需要写对于异常出现时的处理逻辑。
如果我们使用 try...catch... 语句块处理异常,那么当程序出现异常时,程序不会立即停止,而是继续向下将代码执行完,我们仍然拿刚才的的代码举例,选中刚才出现异常的哪一行代码,我们按快捷键 Ctrl + Alt + t,就会出现像屏幕中这样的提示,我们点击 try/catch 环绕,当然也可以自己手动敲,都可以,懒得写的同学可以记住这个快捷键,挺方便的
环绕完成之后,就会变成下图这样的结果
我们再次运行 main 方法,在控制台可以发现,刚才采用JVM默认处理机制没有运行的这行代码在加入 try...catch.. 语句块之后也成功运行了。
catch 语句块中的额 e.pritStackTrace 这行代码的意思在文章下面会说到,这里暂时先不管。
try...catch.. 语句块的好处就是,当程序出现异常时,它不会让程序马上停止,而是会继续向下执行将程序运行完毕。
1.3 使用 throws 和 throw 抛出异常
上面我们说到了自行处理异常采用可以使用 try..catch...包裹代码的方式,下面还有另一种方式,就是采用 throws 和 throw 关键字,捕获异常,这里我们还可以使用关键字 throws 或者 throw 第三种解决异常的方式,即抛出异常。
1.3.1 使用 throws 抛出异常
throws 关键字你是在写在方法定义处,表明声明一个异常,也可以写多个异常,使用格式如下所示
当我们定义一个方法时,如果方法中出现异常,但是又不想使用 try...catch... 进行捕获,我们此时就可以使用 throws,在方法声明处将该方法中可能出现的异常类声明并抛出。
throws 关键字在抛出异常时还有一个点需要注意,如果说出现的异常是编译时异常,必须抛出,如果是运行时异常,也可以选择不抛出。
例子如下,我们定义一个文件输入流对象,这里IDEA爆红了,提示我们有异常需要处理
因为这里有可能我们定义的文件不存在,我们就可以使用 throws 将异常抛出,直接写在方法上,如下所示,我们还可以写多个异常类,类与类之间中间用 "," 隔开。
1.3.2 使用 throw 抛出异常
throw 关键字则使用在方法的内部,可以用来结束方法,它可以手动抛出异常对象,交给调用者,一旦抛出异常对象,那么下方的代码就不会再继续执行了,使用方法如下图所示
代码如下
public static void main(String[] args) {
// 定义一个空数组
int[] arr = null;
// 先对数组做一个判空操作,如果为空抛出异常
if (arr == null){
// 数组为空,抛出异常
throw new NullPointerException("空指针异常");
}
// 因为 throw 抛出异常,所以下方的打印语句不会执行
System.out.println("这句话根本不会执行");
}
运行上方代码,可以在控制台得到如下结果
可以看到,我们定义的 "空指针异常" 输出,说明该异常被抛出来了,而且下方的输出语句也没有执行。说明 throw 抛出异常时会直接结束方法的运行。
2. try...catch.. 捕获到异常之后代码的执行顺序?
当我们使用 try...catch... 捕获异常之后,其语句块内代码是有一定的执行顺序的,如下代码
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
// 第一步,开始进入 try...catch...语句块
try {
// 第二步,执行此行代码,发现此处代码有异常,底层会创建出一个此异常的对象,也就是
// ArrayIndexOutOfBoundsException 数组越界的对象
arr[3] = 3;
// 第三步,将刚才创建的对象与 catch 括号内的对象中做比较,看看能否接收
} catch (Exception e) {
// 第四步:若接收成功,就会执行 catch 语句块内部的这一行代码
// 若接受不成功,不会执行这一行代码,程序直接跳出catch 语句块并不再向下执行,程序停止
e.printStackTrace();
}
System.out.println("看看我会执行吗");
}
使用 try...catch... 捕获异常,我们通常会对异常做处理,上述代码中,括号内创建了 Exception 的对象 e,这里是可以的,因为 Exception 是所有异常类的父类,当出现异常产生异常对象时
底层就会出现 Exception e = new 异常对象,Exception父类引用指向 Exception 子类对象,这是一种多态的写法,各位应该能看得懂,当然如果你在编写代码的时候就能确定代码会出现哪种类型的异常,也可以直接精确的写成 Exception 的子类。
例如上面的例子,我确切知道它会产生数组越界异常,所以直接将 Exception 写成ArrayIndexOutOfBoundsException 也没有问题,其实不只是可以写成这两种,只要是产生的异常类的父类,都可以写在括号内接收子类异常对象。
3. try...catch... 相关的四个面试题
3.1 如果 try...catch... 中没有出现异常,程序如何执行?
我们来验证一下,如下代码
public static void main(String[] args) {
// 第一步,开始进入 try...catch...语句块
try {
int[] arr = new int[3];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
// 直接定义父类 Exception 接受可能出现异常的类对象
} catch (Exception e) {
// 自定义异常处理逻辑,打印一句话
System.out.println("索引越界异常");
}
System.out.println("看看我会执行吗");
}
代码本身没有任何问题,我们直接运行,在控制台得到如下结果
可以看到,程序正常运行,没有任何问题
我们也可以就此得到结论,如果我们的 try...catch... 语句块中没有任何异常出现,程序会把 try 语句块中的代码全部执行完毕,不会执行 catch 语句块中的代码。
3.2 如果 try...catch... 中出现多个异常,程序会怎么执行?
如下代码,我定义两个异常,一个索引越界异常,一个算术运算异常,在 catch 中 我们也定义与之精准对应的两个 catch 异常类,运行代码看啊可能会有什么结果
public static void main(String[] args) {
// 第一步,开始进入 try...catch...语句块
try {
int[] arr = new int[3];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
// 制造一个索引越界异常
arr[3] = 3;
// 制造一个 除0 异常
int a = 10/0;
// 定义数组越界异常的catch语句块处理方案
} catch (ArrayIndexOutOfBoundsException e) {
// 自定义数组越界异常处理逻辑
System.out.println("索引越界异常");
// 定义算术运算异常的catch语句块处理方案
}catch (ArithmeticException e){
// 自定义除0异常处理逻辑
System.out.println("算术运算异常");
}
System.out.println("看看我会执行吗");
}
运行此方法,我们在控制台得到如下结果,可以看到控制台纸打印了"索引越界异常",说明算术运算异常的 catch 语句块根本就没有执行。
那么我们可以得出结论,如果 catch 语句块中有多个异常,那么在接收返回第一个异常之后其余的异常不会接收。
此外,有一点需要做补充,如果有多个异常可能出现,并且我们定义的 catch 语句块中异常类有父子继承关系,那么父类要写在子类的下边,不能写在子类的上边。
看下面我演示的代码,可以看到,当我们定义了一个顶级父类异常Exception 之后,IDEA提示我们后面两个异常有错误
点过去 按Alt + Enter键,IDEA提示我们删除此异常,或者将定义的这个数组越界异常放在 Exception 异常之前。
原因是什么呢?
其实很简单,当我们程序出现问题之后,进入 catch 语句块,它还是会从上向下执行 catch 语句块,你现在把父类定义在了子类的前面,父类就可以把异常接受,那么子类存在的意义何在呢?
因此要记住,当程序出现多个异常时,catch 语句块中父类异常不能在子类异常的前面,必须放在子类异常的后面。
由此我们可以得到以下结论:
如果 try 语句块中出现了多个问题,那么在出现第一个异常的时候,try 语句块中的代码就不会再继续向下执行了,程序会直接跳到 catch 语句块中,判断出现的异常与哪个异常对象相匹配,就会执行 catch 语句块中的内容,如果没有与之相匹配的异常对象,程序会报错。当程序出现多个异常时,catch 语句块中父类异常不能在子类异常的前面,必须放在子类异常的后面。
3.3 如果 try... catch... 中遇到的异常没有被捕获,该怎么执行?
接着刚才的例子,如果我 catch 语句块修改一下,如下代码,程序首先会出现数组越界异常,但在下方 catch 语句中我没有对出现的异常进行捕获,再次运行代码,在控制台得到如下结果
可以看到,程序报错了,但还是只报了数组越界异常,我们定义的算术运算异常并没有显示出来,没有执行算术运算一场语句块中的 sout 输出语句。
由此得出结论,当我们程序爆出的异常没有被 catch 语句块捕获时,会采用JVM默认的异常捕获机制捕获,也就是说,异常没有被捕获,tyr...catch... 代码块无效,白写了。
3.4 如果 try 中遇到了问题,那么下面的其他代码还会执行吗?
如下代码,我在出现异常的语句下方打印一句话,看看是否会执行
public static void main(String[] args) {
// 第一步,开始进入 try...catch...语句块
try {
int[] arr = new int[3];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
// 制造一个索引越界异常
arr[3] = 3;
// 打印一句话
System.out.println("这句话根本不会执行");
// 定义数组越界异常的catch语句块处理方案
} catch (ArrayIndexOutOfBoundsException e) {
// 自定义数组越界异常处理逻辑
System.out.println("索引越界异常");
}
}
运行代码,得到如下结果
可以发现,我们定义在异常下方的输出语句根本没有执行,也证实了我们的结论
如果 try 语句中出现了问题,出现问题的代码下方的代码根本不会执行,它会直接进入到 catch 语句块中,如果catch 没有捕获该异常,还是会交给 JVM虚拟机去处理。
4. 什么时候使用 try...catch... ?什么时候使用 throws或 throw?
如果我们想要知道什么时候应该使用捕获,什么时候应该使用抛出,就要知道它们两个的核心思想。
捕获异常:让程序不要停止,一直运行下去;
抛出异常:告诉调用者出错了;
其实通过上面的讲解,我们也大概明白了,捕获异常是将遇到的异常自己解决,而抛出异常是将遇到的异常向上抛出,抛给该方法的调用者。
可以举个简短的例子,当你在工作的过程中遇到了很麻烦的一个问题,你既可以自己解决它,也可以向你的上级汇报,自己解决就好比是我们在代码中使用 try...catch...,汇报给上级就好比是throws与throw,上级可以继续throws给上级的上级,但是这个问题也不能一直向上抛吧!你见过主管会给老板汇报说工作遇到了大难题解决不了的吗?
这么说,各位应该明白了吧。
在开发过程中,当我的一个方法调用另一个方法时,如果被调用的方法可能出现异常,它就可以将异常抛给调用者,让调用者知并使用 try...catch... 将异常捕获,对异常加以处理,但如果是在第一调用者或者 main 方法中,或者我们想要执行完不中断的业务中,通常或进行 try...catch... 捕获处理,而不会继续向上抛。