NO.8 异常: 愿您归来还是少年 | Java敲黑板系列

本文介绍了两种重要的异常处理技术:自定义异常封装类与异常链。自定义异常封装类能有效收集并保留所有异常信息,避免异常信息丢失。异常链则确保异常信息能够沿函数调用链传递,帮助找到异常产生的根本原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开场白

老铁 :可能大家会有这样一个疑问,异常不就是那几点吗?需要这样分几期来介绍吗?我的回答是:需要,非常有需求且必要。对异常的处理直接决定了我们程序的健壮性。想想程序不就是由所谓的“正常”与“异常”组成的吗?再想想我们程序中正常的业务代码占据了多少?异常处理代码又占据了多少?虽然不能说各占“半壁江山”,但是足以让我们以一种虔诚的心态去正确的了解、运用异常技术。

今天我们来说说异常两项重要技术:自定义异常封装类、异常链。有请两位!

自定义异常封装类

让我们先把思绪拨到昨天文中提到的readTwoFile函数(老铁可移步NO.7 异常: 相处之道 )。当时提了一个问题:如何才能给调用端返回正确的、最初始发生的异常?

在留言区老铁们踊跃给出了相应的解决方案,本文中给出另外一种通用的方法,以后在实际项目中可直接应用。

首先,我们可以分析到问题的原因在于异常被【丢弃】了;为此,一个简单的想法就是我们需要把函数执行过程中被【丢弃】的异常保存下来即可。一个解决思路是:可以定义一个列表,用于包含所有的异常。在函数最后抛出一个异常,该异常包括了上述所有的异常。这样,我们就可以保证不会出现【丢弃】的现象。

第一步:定义一个自定义的异常封装类,用于包含所有的抛出的异常。如下述代码所示:

代码片段1

//自定义异常类
class CustomException extends Exception{
        //用于容纳所有的异常
        private List<Throwable> exceptions = new ArrayList<Throwable>();
        //构造函数;传入一个异常列表
    public CustomException(List<? extends Throwable> es){
        exceptions.addAll(es);
    }
    //获取所有的异常
    public List<Throwable> getExceptions(){
        return exceptions;
    }
}

需要说明的是,CustomException异常只是一个异常容器,可以容纳多个异常,但是它本身并非真正意义上的异常,它只是为了解决可一次抛出多个异常。

第二步:改造业务代码,解决异常【丢弃】的问题。建立一个容纳可能抛出多个异常的容器,然后在可能抛出异常位置均把异常加入到容器内即可。如代码片段2所示。
代码片段2

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class HideException {
    // 自定义异常类
    class CustomeException extends Exception {
        // 此处省略,详见代码片段1
    }

    // 一个函数同时读取两个文件
    public void readTwoFile() throws CustomeException {
        BufferedReader br1 = null;
        BufferedReader br2 = null;
        FileReader fr = null;
        List<Throwable> list = new ArrayList<Throwable>();

        try {
            fr = new FileReader("A.txt"); // 1
            br1 = new BufferedReader(fr);
            int count = br1.read(); // 2
            // process code1....

            fr = new FileReader("B.txt"); // 3
            br2 = new BufferedReader(fr);
            count = br2.read(); // 4
            // process code2
        } catch (FileNotFoundException ffe) {
            list.add(ffe); //防止丢弃异常
        } catch (IOException ie) {
            list.add(ie);//防止丢弃异常
        } finally {
            if (br1 != null) {
                try {
                    br1.close();
                } catch (IOException ie) {
                    list.add(ie);//防止丢弃异常
                }
            }
            if (br2 != null) {
                try {
                    br2.close();
                } catch (IOException ie) {
                    list.add(ie);//防止丢弃异常
                }
            }
        }
        // 检查异常的数目
        if (list.size() > 0)
            throw new CustomeException(list);
    }

    // 测试客户端
    public static void main(String[] args) {
        HideException he = new HideException();
        try {
            he.readTwoFile();
        } catch (CustomeException ce) {
            // 异常处理代码
            // ......
        }
    }
}

敲黑板:

通过上述例子,一方面我们说明如何自定义一个异常封装类;另外一方面解决了如何才能不丢弃异常;然后我们再想想该项技术可以用在哪些业务场景了?比如我们在用户注册时,新用户需要填写多项个人信息,后台会逐一对新用户注册信息的必填项进行正确性检查,可能会出现多个输入项都不能通过检验的情况,为此我们可以把多个错误信息进行封装,建立一个异常容器,检查后返回所有的填写异常。

异常链

定义:代码由一层层的函数调用组成。如果一个函数抛出了异常,一种方法是就地解决;一种方法是调用throw方法将异常抛出给上层调用函数;同理,上层调用函数仍然可以再次调用throw方法继续抛出该方法的异常;如此,就会产生一条由异常构成的异常链。

为什么:对于有些异常如果采取就地解决,就会让上层调用函数不知道是什么原因引起了程序异常,为此就不便于程序排错或用户操作反馈。为此,需要异常链的层层传递,最终让有相应处理职责的函数进行异常处理。

如何传递:以一个实例来说明如何在实际项目如何采用异常链来实现异常传递,如代码片段3所示:

public class ExceptionChainTest {
    class CustomeException extends Exception {
        //1 空构造函数
        public CustomeException() {
            super();
        }
        //2 定义异常原因
        public CustomeException(String message) {
            super(message);
        }
        //3 定义异常原因,并保留原始信息
        public CustomeException(String message, Throwable cause) {
            super(message, cause);
        }
        //4 保留原始信息
        public CustomeException(Throwable cause) {
            super(cause);
        }
    }
    //底层测试函数
    public void func2() throws Exception {
        throw new Exception("func2 exception ....");
    }
    //上层测试函数
    public void func1() throws Exception {
        try {
            func2();
        } catch (Exception ex) {
            throw new CustomeException("func1 exception");//5 
        }
    }
    //客户端测试函数
    public static void main(String[] args) {
        ExceptionChainTest test = new ExceptionChainTest();
        try {
            test.func1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第一步:我们首先自定义了一个异常类:CustomeException。该构造函数中的第//3与第//4种方式是实现异常链的核心,即保留了原始的异常信息。
第二步,我们模拟了一个例子来说明是如何实现异常链传递的,函数调用链是:main函数->func1函数->func1函数。按照执行逻辑,最底层函数func2() 函数会抛出一个Exception类型的异常给上层func1( ) ,func1()函数没有对异常做具体的处理,只是重新生成了一个异常对象抛给main函数。
大家思考一分钟,想想会输出什么信息:

ExceptionChainTest$CustomeException: func1 exception
    at ExceptionChainTest.func1(ExceptionChainTest.java:30)
    at ExceptionChainTest.main(ExceptionChainTest.java:37)

觉得上面的信息是不是少了一点什么?是的,居然丢弃了最原始的引起异常的原因。老铁们想想可以怎么解决了?
解决问题的方法也很简单,就是把代码片段3中的//5

throw new CustomeException("func1 exception");//5 

替换为:

throw new CustomeException("func1 exception", ex); //6 

替换以后,程序运行结果为:

ExceptionChainTest$CustomeException: func1 exception
    at ExceptionChainTest.func1(ExceptionChainTest.java:29)
    at ExceptionChainTest.main(ExceptionChainTest.java:37)
Caused by: java.lang.Exception: func2 exception ....
    at ExceptionChainTest.func2(ExceptionChainTest.java:22)
    at ExceptionChainTest.func1(ExceptionChainTest.java:27)
    ... 1 more

OK!世界又回到了和谐状态,从抛出的异常中我们找到了造成异常的最原始原因。是不是觉得很简单?注意代码片段//5 中,丢失了原始异常信息,而修改后的//6 是真正的异常封装,保留了原始异常信息。

敲黑板:

上层调用函数捕捉到异常后,可以对异常进行封装(如上例中第3、4种构造函数的方式)后再抛出,这样后续调用函数所获得的异常信息就不会丢失,进而就能获得产生异常的根本原因,以便程序员解决问题或反馈给使用用户。

小结

敲黑板,画重点:

  1. 异常需要封装和传递,对待异常,我们不要“吞噬”异常,也不要直接抛出异常,可采取一个异常容器对代码执行过程中抛出的异常进行收集,最后反馈给调用端,如此就不会丢弃异常,方便用户获取产生异常的根本原因。该技术常用于一个函数可能会抛出多种异常的情况。
  2. 异常链也是一种传递异常的实用方法,该技术常用于有一定函数调用深度的业务场景。

转载自公众号:代码荣耀
图1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值