从零开始学Java之异常到底该如何捕获和处理?

作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

在上一篇文章中,壹哥 带大家简单认识了Java中的异常处理机制,但这些内容主要是偏重于理论,接下来我会带大家学习具体的代码实现。现在我们知道,Java的异常处理是通过5个关键字来实现的,即try、catch、throw、throws和finally。try catch语句用于捕获并处理异常,finally语句用于在任何情况下(除特殊情况外,比如提前调用了System.exit()退出虚拟机的方法)都必须执行的代码,throw语句用于拋出异常,throws语句用于声明可能会出现的异常。

虽然如此,但具体该怎么捕获异常?怎么抛出异常?什么时候抛?什么时候捕?这些对初学者来说都是需要认真掌握的。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【3800】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear...

Gitee: 一一哥/从零开始学Java

一. 捕获和处理异常

1. 概述

在Java中,如果某行或某几行代码有可能会抛出异常,我们此时就可以用try ... catch ... finally进行捕获处理。把可能发生异常的语句放在try { ... }语句中,然后使用catch语句捕获对应的Exception及其子类,把必须执行的代码放在finally语句中。接下来我们就来看看具体的代码实现吧。

2. try-catch结构

2.1 基本语法

首先我们来看看try-catch的基本语法:

java 复制代码
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的用法:

java 复制代码
/**
 * @author 一一哥Sun
 */
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()方法:输出异常的类型与性质。

2.3 配套视频

本节内容配套视频链接如下:

player.bilibili.com/player.html...

3. 多重catch结构

我们在编写java代码时,多行语句有可能会产生多个不同的异常,你们面对这个多个异常该怎么处理呢?其实我们可以使用多重catch语句来分别处理多个异常。

3.1 基本语法

多重catch语句的基本语法格式如下:

java 复制代码
try {
    // 可能会发生异常的语句
} catch(ExceptionType e) {
    // 处理异常语句
} catch(ExceptionType e) {
    // 处理异常语句
} catch(ExceptionType e) {
    // 处理异常语句
	...
}

当存在多个catch代码块时,只要有一个catch代码块捕获到一个异常,其它的catch代码块就不再进行匹配。但是我们要注意,当捕获的多个异常类之间存在父子关系时,一般是先捕获子类,再捕获父类。所以我们在编写代码时,要把子类异常放在父类异常的前面,否则子类就会捕获不到。

3.2 代码实现

接下来壹哥给大家设计了一个利用IO流读取文件内容的案例,这段代码就可能会出现两个异常。

java 复制代码
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代码块中,我们可以执行一些清理等收尾善后性质的代码,其基本语法格式如下:

java 复制代码
try {
    // 可能会发生异常的语句
} catch(ExceptionType e) {
    // 处理异常语句
} finally {
    // 执行清理代码块,这里的代码肯定会被执行到,除非极特殊的情况发生
}

上面的代码中,无论是否发生了异常(除极特殊的情况外,比如提前调用了System.exit()退出虚拟机的方法),finally语句块中的代码都会被执行。另外,finally语句也可以直接和try语句配合使用,其语法格式如下:

java 复制代码
try {
    // 逻辑代码块
} finally {
    // 清理代码块
}

我们在使用try-catch-finally语句时要注意以下几点:

  1. 在异常处理的语法结构中,只有try是必需的。如果没有try代码块,则不能有后面的catch和finally;
  2. 虽然catch块和finally块是可选的,但也不能只有try块,catch块和finally块至少要出现其中之一,也可以同时出现;
  3. 可以有多个catch块,捕获父类异常的catch块,必须位于捕获子类异常的后面;
  4. 多个catch块必须位于try块之后,finally块必须位于所有的catch块之后;
  5. 只有finally与try语句块的语法格式,这种情况会导致异常的丢失,所以并不常见;
  6. 通常情况下,我们不应该在finally代码块中使用return或throw等会导致方法终止的语句,否则这将会导致try和catch代码块中的return和throw语句失效。

尤其是try-catch-finally与return的结合,是我们面试时的一个考点哦。有些面试官会贱贱地问你try-catch-finally中如果有return,会发生什么,请大家自行做个实验吧。

4.2 代码实现

接下来壹哥在之前的案例基础上进行改造,引入finally代码块:

java 复制代码
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/**
 * @author 一一哥Sun
 */
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语句块的执行情况可以细分为以下几种情况:

  1. 如果try代码块中没有拋出异常,则执行完try代码块后会直接执行finally代码块;
  2. 如果try代码块中拋出了异常,并被catch子句捕捉,则终止try代码块的执行,转而执行相匹配的 catch代码块,之后再执行 finally代码块;
  3. 如果try代码块中拋出的异常没有被任何catch子句捕获到,将会直接执行finally代码块中的语句,并把该异常传递给该方法的调用者;
  4. 如果在finally代码块中也拋出了异常,则会把该异常传递给该方法的调用者。

4.4 配套视频

本节内容配套视频链接如下:

player.bilibili.com/player.html...

------------------------------正片已结束,来根事后烟----------------------------

二. 结语

至此,壹哥就把今天的内容讲解完毕了,通过今天的内容,大家要注意以下几点:

  • try...catch与if...else是不一样的,try后面的花括号{ }不可以省略,即便try中只有一行代码;
  • 同样的,catch的花括号 { } 也不可以省略
  • 当捕获的多个异常类之间存在父子关系时,一般是先捕获子类,再捕获父类;
  • 在异常处理的语法结构中,只有try是必需的。如果没有try代码块,则不能有后面的catch和finally。

在下一篇文章中,壹哥 会带大家学习如何声明和抛出异常,敬请期待哦。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

相关推荐
考虑考虑39 分钟前
JDK9中的dropWhile
java·后端·java ee
想躺平的咸鱼干1 小时前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying1 小时前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·1 小时前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
martinzh2 小时前
Spring AI 项目介绍
后端
Bug退退退1232 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠2 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
前端付豪2 小时前
20、用 Python + API 打造终端天气预报工具(支持城市查询、天气图标、美化输出🧊
后端·python
爱学习的小学渣3 小时前
关系型数据库
后端
武子康3 小时前
大数据-33 HBase 整体架构 HMaster HRegion
大数据·后端·hbase