通过异常处理错误

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lierming__/article/details/81836076

    ### Java 的基本理念是 “ 结构不佳的代码不能运行 ” 。

    ### Java 使用异常来提供一致的错误报告模型,使得构件能够与客户端

         代码可靠地沟通问题。

    ### 异常处理是 Java 中唯一正式的错误报告机制,并且通过编译器强制

         执行。

》》概念

    ### Java 中的异常处理则建立在 C++ 的基础之上。

    ### 使用异常带来的好处之一是:往往能降低错误处理代码的复杂度。

》》基本异常

    ### 异常情形是指阻止当前方法或作用域继续执行的问题。

    ### 区分异常情形普通问题

          》》普通问题是指,在当前环境下能得到足够的信息,总能处理这个

            问题。

          》》对于异常情形,就不能继续下去了,因为在当前环境下无法获得

           必要的信息来解决问题。

           碰到这种情形,所能做的就是从当前环境跳出,并且把问题提交给上

            一级环境。这就是抛出异常时所发生的事情。

    ### 当抛出异常之后,有几件事会随之发生。首先,同 Java 中其他对象

         的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径

        (它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用

         此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行

         程序。这个恰当的地方就是异常处理程序它的任务是将程序从错误状态

        中恢复,以使程序能要么换一种方式 运行,要么继续运行下去

   ### 异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些

         事务的底线。

   ### 还可以将异常看作一个内建的恢复(undo)系统,因为(在细心使用的情

        况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,

        异常将 “ 恢复 ” 到程序中某个已知的稳定点上。

   ### 异常最重要的一方面就是如果发生问题,它们将不允许程序沿着其正常的

        路径继续走下去。

   ### 异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了

        什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。

   ### 异常参数:

        @@ 所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受

               字符串作为参数,以便能把相关信息放入异常对象的构造器。

        @@

              throw  new  NullPointerException("t= null")

          关键字 throw :  在使用 new 创建了异常对象之后,此对象的引用将传递给

                                  throw

    ###  异常返回的 “ 地点 ” 与普通方法调用返回的 “ 地点 ” 完全不同。(异常将在一

          个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,

          也可能会跨越方法调用栈的许多层次)。

     ###  能够-抛出任意类型的 Throwable 对象,它是异常类型的根类。通常,对于

         不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者

         异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常(通常,

        异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。)

》》捕获异常

     ### 要明白异常是如何被捕获的,必须首先理解监控区域的概念。它是一般可能

         产生异常的代码,并且后面跟着处理这些异常的代码。

     ###  try 块:

           try {

               ..........

           }

     ###   异常处理程序

          @@ 异常处理程序紧跟在 try 块之后,以关键字 catch 表示:

                try {

                 .............

                }catch( Type1  id1 ){

                  ...............

                }catch( Type1  id2 ){

                .....................

               }

         @@  异常处理程序必须紧跟在 try 块之后。当异常被抛出时,异常

            处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后

           进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句

          结束,则处理程序的查找过程结束。

         @@  注意在 try 块的内部,许多不同的方法调用可能会产生异常类型相

          同的异常,而你只需要提供一个针对此类型的异常处理程序。

   ###  终止与恢复

          ### 异常处理理论上有两种基本模型: 终止模型恢复模型

          ### 但是恢复模型不实用,主要原因可能是它所导致的耦合:恢复性的

                处理程序需要了解异常抛出的地点,这势必要 包含依赖于抛出位置的

                非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从

                许多地方抛出的大型程序来说,更是如此。

》》创建自定义异常

         ### 要自己定义异常类,必须从已有的异常类来继承,最好是选择意思相近的

              异常类继承(不过这样的异常类并不容易找)。

         ### 对异常来说,最重要的部分就是类名。

         ###   try {

                ...............

               } catch (Exception  e  )  {

               .   e.printStackTrace ( System.out ) ;      // 信息被发送到了 System.out  ,

                                                                          并自动地被捕获和显示在输出中

                   e.printStackTrace ( ) ;  // 信息将被输出到标准错误流(System.err)

               }

         ### 异常与记录日志

             @@ 使用  java.util.logging  工具将输出记录到日志中。

》》异常说明

        ### 异常说明使用了附加的关键字  throws

              void  f()  throws   TooBig   {

              }

            补充:使用关键字 throws ,声明该方法可能抛出异常(实际上不一定会产生异常)

        ### 使用 throws 声明的异常会在编译时被强制检查,这种异常称为被检查的异常

》》捕获所有异常

        ###  catch( Exception e){

               }

              @@ Exception 将捕获所有的异常,所以最好把它放在处理程序列表的末尾,以防

              它抢在其他处理程序之前先把异常捕获了。

        ###   栈轨迹

             @@ printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问,

             这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的

             一帧。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable

             被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用

        ###   重新抛出异常

             @@ 有时候需要把刚捕获的异常重新抛出,尤其是在使用 Exception 捕获所有的异常的

            时候。

                  catch(Exception e){

                       System.out.println("An Exception was thrown");

                       throw e ;

                  }

              @@ 重抛异常会把异常给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句

             将都被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常处理的

             程序可以从这个异常对象中得到信息。

              @@ 如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常

             抛出点的调用栈信息,而并非新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()

             方法,这将返回一个 Throwable 对象,它是通过对当前调用栈信息填入原来那个异常对象而建立

             的。

                 catch(Exception e){

                       System.out.println("An Exception was thrown");

                       throw (Exception)e.fillInStackTrace() ;    // 执行这一行就成了异常的新发生地了

                  }

              @@ 异常对象都是用 new 在堆上创建的,垃圾回收器会自动把他们删除掉。

        ###   异常链

              @@ 常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常信息保存下来,这被

                称为异常链

              @@ 现在 Throwable() 的子类在构造器中都可以接受一个 cause (因由 )对象作为参数。这个

                cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建

               并抛出了新的异常 ,也能通过这个异常链追踪到异常最初发生的位置。

              @@ 在 Throwable() 的子类中,只有三种基本的异常类提供了带cause 参数的构造器。它们是

               Error(用于Java 虚拟机报告系统错误)ExceptionRuntimeException 。如果要把其他类

              型的异常链接起来,应该使用 initCause() 方法而不是构造器。

》》Java标准异常

             @@ Throwable 这个 Java 类被用来表示任何作为异常被抛出的类。

             @@ Throwable 对象可分为两种类型: Error   和 Exception

             @@  Error 用来表示编译时和系统错误(除特殊情况外,一般不用关心)

             @@  Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能

                     抛出 Exception 型异常。

             @@ 对异常来说,关键是理解概念以及如何使用。

             @@ 异常并非全是在 java.lang 包里面定义的;有些异常是用来支持其他像 util 、 net 和 io

                     这样的程序包的。

         ## 特例:RuntimeException

             @@ 如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常。

             @@ RuntimeException 异常被称为“ 不受检查的异常 ” ,这种异常不需要再异常说明的声明方法

                    中抛出。RuntimeException 异常属于错误,将被自动捕获,就不用你亲自动手了。

             @@ RuntimeException 异常,编译器不需要异常说明,其输出被报告给了 System.err

             @@ 如果 RuntimeException 异常没有被捕获而直达 main() ,那么在程序退出前将调用异常的

                    printStackTrace() 方法。

             @@ 请务必记住:只能在代码中忽略 RuntimeException (及其子类)类型的异常,其他类型异常

                    的处理都是由编译器强制实施的。究其原因,RuntimeException 代表的是编程错误:

                     -----无法预料的错误。比如从你控制范围之外传递进来的 null 引用。

                     -----作为程序员,应该在代码中进行检查的错误。在一个地方发生的异常,常常会在另一个地方

                          导致错误。

             @@值得注意的是:不应把 Java 的异常处理机制当成是一个单一用途的工具。它被设计用来处理一些

                     烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于某些编译

                    器无法检测到的编程错误,也是非常重要的。

》》使用  finally 进行处理

              @@ 对于一些代码,可能会希望无论 try 块中的异常是否抛出,它们都能得到执行。这通常适用于

                    内存回收之外的情况(因为回收由垃圾回收器完成)。为了达到这种效果,可以在异常处理程序

                    后面加上 finally 子句。完整的异常处理程序如下:

                    try{

                       ..........................

                    }catch( ){

                        ........................

                    }catch( ){

                        ......................

                    }finally {

                         .....................

                    }

           ## finally 用来做什么

                @@ 对于没有垃圾回收析构函数的自动调用机制的语言来说,finally 非常重要。

                @@ Java 有垃圾回收机制,所以内存释放不再是问题(Java 也没有析构函数)。

                        那么,Java 在什么情况下才能用到 finally 呢?

                          当要把除内存回收之外的资源恢复到它的初始状态时,就要用到 finally 子句。

                        这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,

                        甚至可以是外部世界的某个开关。

               @@  在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高

                      一层的异常处理程序之前,执行 finally 子句。

               @@  当涉及 break 和 continue 语句的时候,finally 子句也会得到执行。

                       请注意:如果把 finally 子句和带标签的 break 和 continue 配合使用,在 Java

                       里就没有必要使用 goto  语句了。

           ## 在return 中使用 finally

                @@  在try 块的末尾使用 return ,finally 子句也会得到执行。

                @@  因为 finally 子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以

                         保证重要的清理工作仍旧会执行。

                @@  在 finally 类内部,从何处返回无关紧要。

           ## 缺憾: 异常丢失

                @@ 情况一:

                       try{

                            LostMessage lm = new LostMessage();

                           try{

                               lm.f();     //该方法会抛出一个异常a

                           }finally{

                               lm.dispose();  //该方法会抛出一个异常b

                           }

                      }catch( Exception e){

                          System.out.println(e);

                      }

                   说明:上述程序中的 异常a 不会被外层的 catch 捕捉到(丢失了),外层的 catch

                            只能捕获到 finally 子句里面的 异常b

                @@ 情况二:

                      try{

                          throw new  RuntimeException();

                      }finally {

                          return;

                      }

                   说明:上述程序运行时即使抛出了异常,也不会产生任何输出。

》》异常的限制

           @@ 当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常 。

           @@异常限制对构造器不起作用。子类的构造器可以抛出任何异常,而不必理会基类

                  构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用,

                  派生类构造器的异常说明必须包含基类构造器的异常说明

          @@ 派生类构造器不能捕获基类构造器抛出的异常。

          @@ 在继承过程中,编译器会对异常说明做强制要求。但是,异常说明本身并不属于

                  方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能

                基于异常说明来重载方法。

          @@ 一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。

                 这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,

                 在继承和覆盖的过程中,某个特定方法的 “ 异常说明的接口 ” 不是变大了而是变小了

                 ---------------这恰好和类接口在继承时的情形相反

》》构造器

          @@ 在设计异常时有一个问题: 应该把异常全部放在这一层处理,还是先处理一部分,然后再

                 向上层抛出相同的(或新的)异常;又或者是不做任何处理直接向上层抛出。

                 ( 如果用法恰当的话,直接向上层抛出的确能简化编程)

           @@ java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端

                 程序员,这是他们的责任。

           @@ 对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的

                try 子句

                 补充:上面通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:

                 在创建需要清理的对象之后,立即进入一个 try--finally 语句块。

》》异常匹配

            @@ 抛出异常的时候,异常处理系统会按照代码的书写顺序找出 “ 最近 ” 的处理程序。找到

            匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。

            @@ 查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可

            以匹配其基类的处理程序。

             @@ 如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“ 屏蔽 ” 掉。这样

            的话,编译器会向你报告错误。

》》其他可选方式

            @@ 异常处理的一个重要原则是 “ 只有在你知道如何处理的情况下才能捕获异常 ”。

            @@  异常处理的一个重要目标是:把错误处理的代码和错误发生的地点相分离。

            @@ 下面将会提到“ 被检查的异常 “ 及其并发症,以及采用什么方法来解决这些问题。

          ## 历史

             @@ C++ 从 CLU 那里还带来了另一种思想:异常说明。

                    异常说明可能有两种意思:一个是” 我的代码会产生这种异常 , 这由你来处理 “

                                                          另一个是” 我的代码忽略了这些异常,这由你来处理 “

             @@  在Java 中,对于范型用于异常说明的方式存在着一些限制。

          ## 观点

              @@ Java 发明了” 被检查的异常 “

              @@  反射泛型就是用来补偿静态类型检查所带来的过多限制。

              @@ 要理解编译器的能力限制。

              @@ 自动构建过程单元测试是非常重要的。

          ## 把异常传递给控制台

              @@ 最简单而又不用写多少代码就能保护异常信息的方法,就是把它们从 main()

                       传递到控制台

              @@ main() 作为一个方法也可以有异常说明。通过把它传递给控制台,就不必在

                      main() 里写 try-catch 子句了。

          ## 把 “ 被检查的异常 ” 转换为 “ 不检查的异常 ”

               @@ 把” 被检查的异常 “包装进 RuntimeException 里面,如下:

                      try{

                           ..................

                      }catch ( ThisException   e){

                             throw new RuntimeException(e) ;

                      }

               @@

                      法一: 用 RuntimeException 来包装‘ ” 被检查的异常 “

                      法二: 创建自己的 RuntimeException 的子类

》》异常使用指南

            ##  应该在下列情况下使用异常:

                 1)、 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常)

                 2)、解决问题并且重新调用产生异常的方法。

                 3)、进行少许修补,然后绕过异常发生的地方继续执行。

                 4)、用别的数据进行计算,以代替方法预计会返回的值

                 5)、把当前运行环境下能做的事情尽可能做完,然后把相同的异常重抛到更高层

                 6)、把当前运行环境下能做的事情尽可能做完,然后把不同的异常重抛到更高层

                 7)、终止程序

                 8)、进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人)

                 9)、让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资)

》》总结

          @@ 异常处理的优点之一:就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理

                  你编写的这段代码 中产生的错误。

          @@ Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过诸如 C++ 这类

                 语言的长处之一。

     

猜你喜欢

转载自blog.csdn.net/lierming__/article/details/81836076