java程序异常处理

来源:互联网 发布:点windows键没反应 编辑:程序博客网 时间:2024/05/21 07:11
异常处理

       目前,衡量一门语言是否成熟,异常处理是重要的方面。

       增加异常处理之后,程序会具有更好的容错性,会更加健壮。


比如一个简单的程序:

       让用户输入2个数字,结算它们的除法的结果:

       (1)分别获取用户输入的每个子串
       (2)每个子串强转为一个数字。
       (3)计算他们的结果。

      软件是什么? 软件是一个虚拟的世界。

         用户会有一些习惯、操作,并不符合我们程序的期望,或程序运行过程中,总会有些情况, 超出程序的预料……
     如果没有很好的错误处理(如果没有考虑这些情况),程序就会报错、中止。

      为了让程序能真正正确——不管用户如何操作,程序应该能运行良好,我们需要做大量的判断。
        - 判断用户是否输入了2个子串,
        - 判断用户是否输入非数字
        - 判断用户输入的除数是否为0

      于是程序需要这样写(传统的错误处理流程):
        if(错误1)
        {
             // 处理错误
             // retry
 
        }
        if(错误2)
        {
             // 处理错误
             // retry
 
        }
        if(错误3)
        {
             // 处理错误
             // retry
 
        }
        ...
        else
        {

        }

传统错误处理机制,主要如下两个缺点:
      - 无法穷举所有异常情况:因为人类知识的限制,异常情况总比可以考虑到的情况多,总有“漏网之鱼”的异常情况,所以程序总是不够健壮。
      - 错误处理代码和业务实现代码混杂:这种错误处理和业务实现混杂的代码严重影响程序的可读性,会增加程序维护的难度


      考虑将上面写法改为如下形式:

        if(任何错误)
        {
             // 处理错误
             // retry
        }
        else
        {
             // 业务处理
        }
 
       但上面的任何错误这个条件,无法用计算机语言来表达,因此考虑写成如下形式:
 
       try
       {
           // 业务处理
       }
       catch(异常类 异常变量名) ____只要有异常,都会被catch块捕捉,从而进入catch块。
       {

       }

▲ 用try catch来捕捉异常

      (1) 业务代码代码执行过程中,如果出现错误,系统会将该错误包装成异常对象,并提交给JRE
          这个过程叫抛出异常。
      (2) JRE收到异常后,JRE会依次寻找try块之后的多个catch块,如果异常对象是catch后的形参类型
          或其子类的实例(instanceof),程序就会进入对应的catch块,这个过程叫捕获异常。

       catch块可以有0~N个,N个catch块分别捕捉不同类型的异常。

       多个catch块,应该先捕捉小异常(子类异常)。

▲ Java的异常体系:
 
              Throwable
           ↗            ↖
        Error              Exception
    ↗      ↖            ↗              ↖
IOError LinkedError     RuntimeException    IOException
                       ↗               ↖
                 NullPointerException  ClassCastException


▲ Java 7的多异常捕捉:
      
        传统情况下, 每个catch块后只有一个异常类型,因此每个catch块只能捕捉一个异常。

        Java 7的多异常捕捉可以让个catch块后包含多个异常类型

       多异常捕捉的优点:代码更加简洁。
                   缺点:由于一个catch块捕捉了多个异常,因此程序无法分清到底是哪种异常情况。

       多异常捕捉的注意点:catch块中异常型形参,相当默认有一个final修饰。

▲ 访问异常信息:

       当我们用catch块捕捉到异常之后,程序可能需要了解异常发生详细情况,此时就可通过异常对象进行获取:
     - getMessage():返回该异常的详细描述字符串。
     - printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
     - printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。
     - getStackTrace():返回该异常的跟踪栈信息。

      异常的跟踪栈信息,信息非常丰富,可以向我们提供异常依次触发的“轨迹”,
      因此当我们调试程序时,一定要找第一个at、我们自己的写的类所导致的异常。

▲ finally

      代表一定会执行的块。作用是用于保证关闭、回收物理资源(比如网络连接、数据连接、文件IO资源等)。

      放在finally,即可保证关闭资源的代码一定会执行。
         如果finally块包含了return语句,那么该return语句会直接结束方法。


      - finally不怕return,return无法阻止finally的执行。
      - 退出虚拟机(System.eixt(0))可以阻止finally的执行。

      完整的语法格式为:

      try
      {
           //  业务代码
      }
      catch(异常1 ex1)       0~N次
      {

      }
      finally               0~1次
      {

      }
      但try不能单独存在,catch、finally不能同时出现零次。

▲ Java 7的自动关闭资源

      传统的资源关闭,程序代码十分臃肿。

      如果使用Java7的自动关闭资源的try语句,可以既没有finally,也没有catch。

对于自动关闭资源的try语句, 可以没有catch和finally——try块可以单独地存在。
     
      自动关闭资源的try语句,有两个注意点:
        - 只有放在try后面的圆括号里的资源才会被关闭。
          自动关闭的资源必须在try后的圆括号内声明、并初始化。
          自动关闭的资源只在try块中有效。
        - 能被自动关闭的资源必须实现Closeable 或 AutoCloseable接口。


      
▲ checked异常与runtime异常

      runtime异常 - 所有RuntimeException的实例,或RuntimeException的子类的实例,都是runtime异常。
                    它属于运行异常,程序员想捕捉、就捕捉,不捕捉也可以。
                    即使一个程序编译时,没有任何问题。但运行时有可能会引发错误。

      ArithmeticException, ClassCastException,IllegalArgumentException, IndexOutOfBoundsException,
      NullPointerException.  NumberFormatExcpetion


      checked异常 - 一个异常,不是runtime异常,就是checked异常。
                    编译器会强制检查checked异常,如果一个行代码可能引发checked异常,
                    编译器要求程序要么显式用try...catch捕捉该异常,要么使用throws声明抛出异常。

      checked异常的争议:
           缺点:编译器强制程序员要么用try...catch捕捉该异常,要么用throws声明抛出异常。
                 你知道如何处理该异常,就用catch捕捉、并处理该异常;如果不知道处理,用throws抛给别人处理。
                 因此导致编程比较繁琐。
                 实际上,只有Java有checked异常。
           优点:checked异常时体现了一个设计哲学:没有完善的异常处理的代码,不应该有执行的机会。

      throws —— 1。不能独立使用,只能放在方法签名上使用,用于声明抛出某个异常类。
          2。throws可以同时声明抛出多个Checked异常类名。 
            备注:只有存在checked异常的前提下,throws关键字才有存在的意义。

▲ throws与方法重写

       方法重写:2同(方法名、形参列表)2小(返回值类型、声明抛出的异常)1大(访问权限)

▲ throw抛出异常:

     throw 异常实例;

       - throw 异常实例;是一个可执行性的语句,只能放在方法、构造器、初始化块
       - throw后面只能是一个异常对象。

      在有些事情,用户输入的数据、提供的参数,与我们的业务规则不符合,编译器也无法保证我们业务规则,
      此时就必须有成员来显式使用throw抛出异常。

      如果你自己throw了一个checked异常,同样要么用try...catch捕捉,要么用throws声明抛出。
      ——但通常都是用throws声明抛出。


▲ 自定义异常:

       当程序出现了与业务不符合的情况时,此时程序就应该使用throw来抛出异常。
       此时,大部分应该都应该抛出项目自定义异常(因为系统的特定的异常类,本身就代表了一种具体的异常情况)

       最好是异常的类名能让人做到“见名思义”,可以通过异常名表现出程序到底出现了什么问题。

       如果要定义普通异常,继承Exception。
       如果要定义runtime异常,继承RuntimeException。

       通常来说,异常类体只要定义2个构造器即可,其中一个带String参数的构造器中,只要一行简单的super(msg)即可。

▲ 异常转译、异常链。

        一个异常出现后,
                 - 不知道在如何处理, throws声明抛出。
                 - 怎么如何处理,catch到之后,把异常处理完,无需其他方法继续处理
              
                    catch到异常之后,可以进行部分处理,不能处理完。
                    因此异常还需要留给下一个调用者继续处理,此时就需要再次使用throw抛出一个自定义异常

        catch块中,再次使用throw来抛出业务相关的自定义异常。
            ——这就会把原始异常,转译成的新的异常,这个过程被称为异常转译。


0 0
原创粉丝点击