zoukankan      html  css  js  c++  java
  • java —— 异常(三)

    问题引入看如下测试代码:

    import java.io.*;
    
    
    public class HideException {
    
    //一个函数同时读取两个文件
    
    public void readTwoFile() throws FileNotFoundException, IOException{
    
        BufferedReader br1 = null;
    
        BufferedReader br2 = null;
    
        FileReader fr = null;
    
        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
    
        }finally{
    
            if(br1 != null)
    
                br1.close(); //5
    
            if(br2 != null)
    
                br2.close(); //6
    
        }
    
    }
    
    
    
    //测试客户端
    
    public static void main(String[] args){
    
        HideException he = new HideException();
    
        try {
    
            he.readTwoFile();
    
        } catch (FileNotFoundException e) {
    
            //...
    
            e.printStackTrace(); //7
    
        } catch (IOException e) {
    
            //...
    
            e.printStackTrace(); //8
    
        }
    
    }
    
    }
    源码

      如代码片段3所示,readTwoFile用来读取两个文件。其中语句//1与语句//3有可能会抛出FileNotFoundException;语句//2、//4、//5、//6有可能会抛出IOException。按照对控制流的执行逻辑分析,上述代码中的finally子句是无论如何都会被执行的。当//3、//5同时发生异常时,会执行main函数中的哪条/哪些语句?

      答案揭晓:当//3产生了一个FileNotFoundException时,控制流会转向到finally子句,但是其中//5又发生了IOException,那么这个时候返回main调用端的将会是IOException而不是FileNotFoundException因为//3产生的异常被//5抛出的异常所覆盖了。为此,将执行main函数中的语句//8

    重点一个函数尽管抛出了多个异常,但是只有一个异常可被传播到调用端。

      记住:最后被抛出的异常时唯一被调用端接收的异常,其他异常都会被吞没掩盖。如果调用端要知道造成失败的最初原因,程序之中就绝不能掩盖任何异常。

    一、自定异常封装类

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

      

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

    代码片段1

      

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

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

    代码片段2

      

    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方法继续抛出该方法的异常;如此,就会产生一条由异常构成的异常链。

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

      异常链实现异常传递如下代码:

    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函数。

    大家思考一分钟,想想会输出什么信息:

        

      觉得上面的信息是不是少了一点什么?是的,居然丢弃了最原始的引起异常的原因。老铁们想想可以怎么解决了?

    解决问题的方法也很简单,就是把代码片段中的//5

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

      替换为:

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

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

        

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

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

     三、小结

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

    2. 异常链也是一种传递异常的实用方法,该技术常用于有一定函数调用深度的业务场景。

     本文来自该公众号——程序员Chatbook

         

  • 相关阅读:
    JAVA 关键字
    github 上传代码到仓库

    创建链表及使用
    关于MAP文件的使用(转贴)
    styledcomponent使用(一)
    关于EDM模型中多个实体之间循环引用导致保存数据失败的解决方案一例
    C#格式化字符串
    [原]存取AVD设备SD卡中的文件
    NSStirng、NSArray、以及枚举(Method小集合)
  • 原文地址:https://www.cnblogs.com/SacredOdysseyHD/p/8411567.html
Copyright © 2011-2022 走看看