异常处理

170 阅读8分钟

异常处理

异常分类

    异常是在JDK中定义的一组专门表示各种不正确情况的类。一旦发生了对应的不正确情况,
    
    那么JVM就会产生该类的对象(异常对象)。如果,我们的程序没有处理该异常对象,那么
    
    这个异常对象会跟随我们程序的调用流程,一层层往上传递,直到被处理掉。如果到了main
    
    方法都还没有处理,那么就会结束程序,然后打印异常信息在控制台。

    我们通常看到的异常信息,包括了如下几个重要内容:

    1、发生在哪个线程中?

        Exception in thread "main"

    2、发生了什么异常?

        异常类的类名通常比较长,但是完整的描述了异常的类型。

        ArrayIndexOutofBoundsException

        NullPointerException

        ClassCastException

        InputMismatchException

    3、异常发生的代码位置?

        从上往下找第一行自己写的代码。

    在JDK的设计中,异常就是一种类。它有庞大的继承结构。

    最顶级的: Throwable

    次级: Exception (异常)

           Error     (错误)

    区别:

        异常 -- 代码级别可以解决的问题

        错误 -- 系统、环境、硬件等问题

    我们需要搞定的是异常

    异常的子类:

        异常的子类非常多,除了Runtime Exception(及其子类),其他的都是编译时异常。

    编译时异常时指在“编译期”,打红线,告知我们这里有未处理的异常;

    但是,编译期打红线的,还有语法错误呀等等。要区分。
    
    牢记:只有指到红线,明确告知我们有未处理的”某某Exception“才是编译时异常。

    由于继承的关系,我们都知道最重要的方法都是定义在父类当中的。所以,异常类里面我们
    
    最常用的方法是定义在Throwable当中的。

    其中最重要的两个方法是:

    1、getMessage() --- 该方法是返回这个异常对象中的“详细信息”的。

    2、printStackTrace() --- 该方法是打印异常的堆栈信息,使用的是System.err做的打印。

异常的处理

异常处理分两种手段:

    1、事前处理

        当异常还没有发生的时候,我们根据经验判断这里有可能发生异常,然后书写上提前判
        
    断的代码,把异常扼杀,让它根本不会发生。

    2、事后处理 (try - catch - finally)

        这个三个关键字合在一起,共同组成了异常处理的代码块,语法如下:

        ```java        

            try{

                正常逻辑下书写的代码,

                但是这个代码有可能发生异常,我们让它试着执行

            }catch(要处理的异常类 对象){

                处理异常的代码

            }finally{

                不管是否发生异常都必须要执行的代码
            }

        ```

    首先,三个关键字并不是必须同时使用。try后面可以接catchfinally,但是catchfinally不能单独出现。所以有三种组合:

        try - catch

        try - finally

        try - catch - finally

1、try-catch

    try块里面放的是正常逻辑的代码,但这段代码有可能发生异常,所以让他在try当中试着
    
    运行。如果try块中的代码没有发生异常,那么程序流程在执行完try当中所有的代码之
    
    后,直接跳到catch块的后面,继续顺序往下执行。

    如果try块中的代码发生了异常,那么就会停止try块中后续代码的执行,然后JVM拿着这个
    
    异常对象,去和catch块中声明的异常变量进行类型匹配。如果匹配上,JVM就认为你捕获
    
    住了异常,那么异常就会认为你已经解决了,就算你在catch块中一句代码都不写,它也认
    
    为异常被你解决了,不会再发生终止程序,然后带这异常对象向上传播的情况。

    而catch块当中,有没有捕获住异常,不是以来于catch块里面的代码,而是依赖于catch
    
    后面的"()"当中声明异常变量的类型!只要这个类型与本次发生的异常对象的类型匹配上,
    
    那么就捕获住。

    当一个try块里面有可能发生多种异常的时候,那么可以书写多个catch块。每个catch块捕
    
    获一种异常。 在进行匹配的时候,是按照从上往下的顺序,依次匹配每个catch块。谁匹
    
    配上了就进入谁内部。

    *如果这多个catch块捕获的异常类型没有继承关系,那么它们也没有谁先谁后的书写顺
    
    序。但是一旦有继承关系,那么把捕获子类异常的catch块要写在前面,捕获父类异常的
    
    catch块要写在后面*       

2、try-catch-finally

    很明显,是在try-catch后面加了一个finally语句块。

    finally语句块,书写在最后一个catch块的后面。它里面代码,在执行流程当中,是表示
    
    不管是否发生异常,都必须要执行的代码。

    finally当中的这个“必须”,是非常强硬的,也就算遇到return语句。他也必须要执行,
    
    在代码级别只有"System.exit(0)"可以阻止它。
    

try-catch到底写在哪里?

    从“让程序不应为异常而崩溃掉”的角度来讲,那么对于我们来说从main方法开始,可以在
    
    每层调用处做try-catch,都可以达到这个效果。

    但是,在不同层做try-catch,最终执行的效果是不一样的。 你在哪一层进行了处理,那
    
    么就从这一层才开始恢复正常,而它之下的全部被忽略掉了正常执行。

    在实际应用当中,try-catch真正的难点不是它的语法,而是它在处理以后,场景仍然是一
    
    个流畅的。
    
    **JDK7之后的特殊语法******

    在原生语法当中,如果一个try块有可能发生多种异常,那么我们要书写多个catch块,每
    
    个catch块截获一种异常。

        ```java

            try{


            }catch(异常1){


            }catch(异常2){


            }catch(异常3){


            }

        ```

    那么JDK7当中,设计了一种新的语法,允许在一个catch块当中,捕获多种异常。

        ```java

            try{


            }catch(异常1 | 异常2){


            }

        ```

    注意点:

    1、|符号是分隔异常类型的,而不是分隔两个异常变量;

    2、| 符号两边的异常类型不能有继承关系。因为既然取了父类异常,就没有子类异常的事
    
    儿了,因为父类引用可以指向子类对象。
    
    finally到底应该写什么代码?

    finally是不管是否发生异常,都必须要执行的代码写在它当中。那么什么样的代码才属于
    
    这种情况呢?

    在现实开发当中,finally当中的代码往往是“资源的清理”,“管道的关闭”,“链接的断开”
    
    等等,这种回收清理动作。

    不要先写操作代码,而是要先写finally然后在里面关闭它们,再把鼠标跳到中间书写操作代码。

    但是,现在不用了,在JDK8之后有一种新的专门针对于需要关闭资源的异常处理代码块,叫
    
    做”try with resource“。

    如果finally之前有return语句,那么它到底在什么时候被执行的?

各种异常如何处理呢?

    1、运行时异常,对于一个成熟的Java程序员来说,通常都不是用try-catch处理的。

        几乎都是用修改代码或提前判断的方式处理。

        数组下标越界,就该去检查为什么会赋值了一个超过最大下标的数;

        空指针异常,就该去检查谁为null;

        类型转换异常,就该去检查为什么类型不匹配,可不可以做instanceof判断。

    2、编译时异常的时候,为了能够通过编译,我们才会大量使用try-catch

        异常的抛出

        有些方法在实现的过程中,我们会发现可能在执行时出现一些异常情况,很多时候这些
        
        异常情况可能是由业务照成的,不是JVM发现的运行异常。

        所以,在实际开发中,我们有可能主动抛出一个异常对象,表示出现了业务异常。

        语法:throw 异常对象;

        由上面这个语法又触发了Java当中异常类设计的初衷。因为异常在Java看来也是有区
        
        别的,有些是要求必须在编码的时候进行解决(编译时异常);有些则是在运行的时候
        
        报错就可以了,再来修改代码解决。

        那么,我们主动throw的异常对象也要遵循这个机制。如果这个异常对象时运行时异常
        
        对象(RuntimeException及其子类的对象),那么不用做任何额外的动作,编译直接
        
        成功。但如果我们throw的是一个编译时异常对象,那么也应该在编译期告知调用者,
        
        本方法有可能抛出异常,你要处理!所以,这个时候就要在方法申明处配合throws的
        
        语法,才能达到编译期警告调用者的效果。

        这个效果就是,调用方法完成调用后,会红线报错,报到的是“未处理的异常 某某

        Exception”。

        然后调用者要想解决,那么要么加try-catch;要么继续用throws往上抛。取决于,
        
        要解决这个问题的责任者是自己,或自己的再上级调用者。