学习Java之异常到底该如何捕获和处理
作者:一一哥Sun
一. 捕获和处理异常
1. 概述
在Java中,如果某行或某几行代码有可能会抛出异常,我们此时就可以用try ... catch ... finally进行捕获处理。把可能发生异常的语句放在try { ... }语句中,然后使用catch语句捕获对应的Exception及其子类,把必须执行的代码放在finally语句中。接下来我们就来看看具体的代码实现吧。
2. try-catch结构
2.1 基本语法
首先我们来看看try-catch的基本语法:
try { // 可能发生异常的语句 } catch(Exception e) { // 处理异常语句 }
在上面的语法中,我们要把可能引发异常的语句封装在try语句块中,用于捕获可能发生的异常。catch语句里的( )中,要传入与try语句里匹配的异常类,指明catch语句可以处理的异常类型。
也就是说,如果此时try语句块中发生了某个异常,那么这个相应的异常对象就会被拋出,产生异常的这行代码之后的其余代码就不会再被执行。然后catch语句就开始执行,把try中拋出的异常对象进行捕获并处理。
当然,如果try语句块中没有发生异常,那么try里的代码就会正常执行结束,而后面的catch就会被跳过。
这里我们需要注意:try...catch与if...else是不一样的,try后面的花括号{ }不可以省略,即便try中只有一行代码。同样的,catch的花括号 { } 也不可以省略。另外,try语句块中声明的变量属于局部变量,它只在try中有效,其它地方不能访问该变量。
2.2 代码实现
接下来设计一个代码案例,来讲解try-catch的用法:
/** * @author */ public class Demo01 { public static void main(String[] args) { //定义一个长度为3的数组 int[] array = new int[3]; try { //索引超出了数组长度,将会引发ArrayIndexOutOfBoundsException数组下标越界异常 array[3] = 1; //发生异常后,这行代码并不会执行 System.out.println("数组:" + array.toString()); } catch (ArrayIndexOutOfBoundsException e) { //指出异常的类型、性质、栈层次及出现在程序中的位置 e.printStackTrace(); //输出错误的原因及性质 System.out.println("数组越界:" + e.getMessage()); //输出异常的类型与性质 System.out.println("数组越界:" + e.toString()); } } }
在上面这段代码中,小编在try语句中创建了一个长度为3的整数数组,并尝试着将第4个位置上的元素值设为1。由于数组越界,这会引发代码故障,java会抛出一个ArrayIndexOutOfBoundsException异常。由于发生了异常,所以后面的数组输出语句就不会被执行。
而我们在catch中接收了ArrayIndexOutOfBoundsException异常,这个异常与try中抛出的异常是一致的,所以代码会跳转到catch中进行异常的处理。另外在上面的处理代码块中,我们可以通过Exception异常对象的以下方法,来获取到相应的异常信息。
- printStackTrace()方法:输出异常栈信息,指出异常的类型、性质、栈层次及出现在程序中的位置;
- getMessage()方法:输出错误的性质;
- toString()方法:输出异常的类型与性质。
3. 多重catch结构
我们在编写java代码时,多行语句有可能会产生多个不同的异常,你们面对这个多个异常该怎么处理呢?其实我们可以使用多重catch语句来分别处理多个异常。
3.1 基本语法
多重catch语句的基本语法格式如下:
try { // 可能会发生异常的语句 } catch(ExceptionType e) { // 处理异常语句 } catch(ExceptionType e) { // 处理异常语句 } catch(ExceptionType e) { // 处理异常语句 ... }
当存在多个catch代码块时,只要有一个catch代码块捕获到一个异常,其它的catch代码块就不再进行匹配。但是我们要注意,当捕获的多个异常类之间存在父子关系时,一般是先捕获子类,再捕获父类。所以我们在编写代码时,要把子类异常放在父类异常的前面,否则子类就会捕获不到。
3.2 代码实现
接下来给大家设计了一个利用IO流读取文件内容的案例,这段代码就可能会出现两个异常。
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; /** * @author 一一哥Sun */ public class Demo02 { //多重catch语句 public static void main(String[] args) { //定义一个缓冲流对象,以后在IO流阶段壹哥会细讲 BufferedReader reader = null; try { //对接一个file.txt文件,该文件可能不存在 reader = new BufferedReader(new FileReader("file.txt")); //读取文件中的内容。所有的IO流都可能会产生IO流异常 String line = reader.readLine(); while (line != null) { System.out.println(line); line = reader.readLine(); } } catch (FileNotFoundException e) { //处理文件不存在时的异常 System.out.println("文件不存在:" + e.getMessage()); } catch (IOException e) { //处理IO异常 System.out.println("读取文件失败:" + e.getMessage()); } } }
在这段代码中,我们尝试打开一个名为file.txt的文件,并逐行读取其内容。如果该文件不存在或读取失败,程序将会在相应的catch块中处理异常,并打印出异常消息。具体地来说,就是try代码块的第16行代码调用了FileReader的构造方法,这里有可能会发生FileNotFoundException异常。而第18行调用BufferedReader输入流的readLine()方法时,有可能会发生IOException异常。
因为FileNotFoundException异常是IOException异常的子类,所以我们应该先捕获 FileNotFoundException异常,即第23行代码;后捕获IOException异常,即第26行代码。但是如果我们将FileNotFoundException和IOException异常的捕获顺序进行调换,那么捕获FileNotFoundException异常的代码块永远也不会执行,所以FileNotFoundException异常也永远不会被处理。当然,如果多个异常之间没有父子关系,则其顺序就无所谓了。
4. try-catch-finally结构
在上面的代码中,我们涉及到了IO流的相关内容,这一块我们还没有开始进行学习,会在以后给大家进行详细地讲解。
IO流属于一种比较消耗物理资源的API,使用完之后应该把IO流进行关闭,否则就可能会导致物理资源被过多的消耗。那么我们该在哪里关闭IO流呢?有的人会说,我们可以在try语句块执行完正常的功能后关闭IO流。但是大家要知道,try语句块和catch语句块有可能因为异常并没有被完全执行,那么try里打开的这些物理资源到底要在哪里回收呢?
为了确保这些物理资源一定可以被回收,异常处理机制给我们提供了finally代码块,且Java 7之后又提供了自动资源管理(Automatic Resource Management)技术,更是可以优雅地解决资源回收的问题。
4.1 基本语法
无论是否发生异常,finally里的代码总会被执行。在 finally代码块中,我们可以执行一些清理等收尾善后性质的代码,其基本语法格式如下:
try { // 可能会发生异常的语句 } catch(ExceptionType e) { // 处理异常语句 } finally { // 执行清理代码块,这里的代码肯定会被执行到,除非极特殊的情况发生 }
上面的代码中,无论是否发生了异常(除极特殊的情况外,比如提前调用了System.exit()退出虚拟机的方法),finally语句块中的代码都会被执行。另外,finally语句也可以直接和try语句配合使用,其语法格式如下:
try { // 逻辑代码块 } finally { // 清理代码块 }
我们在使用try-catch-finally语句时要注意以下几点:
在异常处理的语法结构中,只有try是必需的。如果没有try代码块,则不能有后面的catch和finally;
虽然catch块和finally块是可选的,但也不能只有try块,catch块和finally块至少要出现其中之一,也可以同时出现;
可以有多个catch块,捕获父类异常的catch块,必须位于捕获子类异常的后面;
多个catch块必须位于try块之后,finally块必须位于所有的catch块之后;
只有finally与try语句块的语法格式,这种情况会导致异常的丢失,所以并不常见;
通常情况下,我们不应该在finally代码块中使用return或throw等会导致方法终止的语句,否则这将会导致try和catch代码块中的return和throw语句失效。
尤其是try-catch-finally与return的结合,是我们面试时的一个考点哦。有些面试官会贱贱地问你try-catch-finally中如果有return,会发生什么,请大家自行做个实验吧。
4.2 代码实现
接下来在之前的案例基础上进行改造,引入finally代码块:
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; /** * @author */ public class Demo03 { //try-catch-finally语句 public static void main(String[] args) { //定义一个缓冲流对象,以后在IO流阶段壹哥会细讲 BufferedReader reader = null; try { //对接一个file.txt文件,该文件可能不存在 reader = new BufferedReader(new FileReader("file.txt")); //读取文件中的内容。所有的IO流都可能会产生IO流异常 String line = reader.readLine(); while (line != null) { System.out.println(line); line = reader.readLine(); } } catch (FileNotFoundException e) { //处理文件不存在时的异常 System.out.println("文件不存在:" + e.getMessage()); } catch (IOException e) { //处理IO异常 System.out.println("读取文件失败:" + e.getMessage()); } finally { try { //在finally代码块中也可以进行try-catch的操作 if (reader != null) { //关闭IO流 reader.close(); } } catch (IOException e) { System.out.println("关闭文件失败:" + e.getMessage()); } } } }
在这段代码中,我们尝试打开一个名为file.txt的文件,并逐行读取其内容。如果文件不存在或读取失败,程序将在相应的catch块中处理异常,并打印出异常消息。无论是否发生异常,程序都会在finally块中关闭文件。
4.3 小结
在try-catch-finally组合的结构中,其执行流程如下图所示:
根据该流程可知,try-catch-finally语句块的执行情况可以细分为以下几种情况:
如果try代码块中没有拋出异常,则执行完try代码块后会直接执行finally代码块;
如果try代码块中拋出了异常,并被catch子句捕捉,则终止try代码块的执行,转而执行相匹配的 catch代码块,之后再执行 finally代码块;
如果try代码块中拋出的异常没有被任何catch子句捕获到,将会直接执行finally代码块中的语句,并把该异常传递给该方法的调用者;
如果在finally代码块中也拋出了异常,则会把该异常传递给该方法的调用者。
二. 结语
至此,就把今天的内容讲解完毕了,通过今天的内容,大家要注意以下几点:
- try...catch与if...else是不一样的,try后面的花括号{ }不可以省略,即便try中只有一行代码;
- 同样的,catch的花括号 { } 也不可以省略;
- 当捕获的多个异常类之间存在父子关系时,一般是先捕获子类,再捕获父类;
- 在异常处理的语法结构中,只有try是必需的。如果没有try代码块,则不能有后面的catch和finally。
以上就是学习Java之异常到底该如何捕获和处理的详细内容,更多关于Java异常捕获和处理的资料请关注脚本之家其它相关文章!