美文网首页
java异常处理机制

java异常处理机制

作者: 劲火星空 | 来源:发表于2019-07-29 20:53 被阅读0次

    程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常,那么异常发生之后怎么办,Java提供了更加优秀的解决办法-异常处理机制。异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

    • 异常分类
    • 两种异常处理方式
    • 异常被吃掉了
    • 注意点

    异常分类

    根据发生原因可分为三类:
    (1)用户输入了非法数据
    (2)要打开的文件不存在
    (3)网络通信时连接中断,或者JVM内存溢出

    根据类型可分为三类:
    (1)检查性异常:用户错误或问题引起的异常,无法预见的。例如要打开一个不存在文件时,一个异常就发生了
    (2)运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略
    (3)错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略

    异常体系结构
    Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类,在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception,如下图所示:

    在这里插入图片描述
    java中定义的异常(Exception)和错误(Error)都继承自Throwable类。其中错误的产生大多是由于运行环境jvm导致的,这部分错误我们通过程序很难纠正,而Java的异常分为两种,checked异常(编译时异常)和Runtime异常(运行时异常)

    编译时异常:checked异常都是可以再编译阶段被处理的异常,所以它强制程序处理所有的checked异常,java程序必须显式处理checked异常,如果程序没有处理,发生错误,无法通过编译
    运行时异常:Runtime异常无须处理也可以通过编译。所有的Runtime异常原则上都可以通过纠正代码来避免

    异常处理的几个关键字:

    关键字 说明
    try 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出
    catch 用于捕获异常。catch用来捕获try语句块中发生的异常
    finally finally语句块总是会被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止
    throw 用于抛出异常
    throws 用在方法签名中,用于声明该方法可能抛出的异常

    既然说java的异常都是一些异常类的对象,那么这些异常类也有一些方法我们应该了解:

    方法 说明
    getMessage() 返回该异常的详细描述字符
    printStackTrace() 将该异常的跟踪栈信息输出到标准错误输出(异常链)
    printStackTrace() 将该异常的跟踪栈信息输出到指定的输出流
    getStackTrace 返回该异常的跟踪栈信息

    两种异常处理方式

    1. try…catch捕获异常
    java提出了一种假设,如果try中的语句一切正常那么将不执行catch语句块,如果try中语句出现异常,则会抛出异常对象,由catch语句块根据自己的类型进行捕获。若没有相应的catch块,则抛出。
    所以其执行步骤可以总结为以下两点:
    (1) 如果执行try块中的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给java运行环境,这个过程称为抛出(throw)异常。
    (2) 当java运行环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的cathc块并把该异常对象交给catch块处理,那这个过程称为捕获(catch)异常;如果java运行时环境找不到捕获异常的catch块,则运行时环境终止,java程序也将退出。

    private void tryCatchWay() {
        FileInputStream fis=null;
        try {
            fis=new FileInputStream("a.txt");
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
            // return语句强制方法返回
            return;
            // 使用exit来退出虚拟机
            //System.exit(1);
        }finally{
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                fis=null;
            }
            System.out.println("fis资源已被回收");
        }
    }
    

    执行结果如下:

    I/System.out: a.txt (No such file or directory)
                  fis资源已被回收
    

    如果使用exit而不是return,那么将会得到如下结果:

    I/System.out: a.txt (No such file or directory)
    

    以上两种情况显示:除非在try块或者catch块中调用了退出虚拟机的方法(即System.exit(1);),否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行的。

    当程序执行try块,catch块时遇到return语句或者throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这两个语句,而是去寻找该异常处理流程中的finally块,如果没有finally块,程序立即执行return语句或者throw语句,方法终止。

    如果有finally块,系统立即开始执行finally块,只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句,如果finally块里也使用了return或throw等导致方法终止的语句,则finally块已经终止了方法,不用再跳回去执行try块、catch块里的任何代码了。所以,一般情况下,不要在finally块中使用return或throw等导致方法终止的语句,因为一旦使用,将会导致try块、catch块中的return、throw语句失效。

    以下四种情况将会导致finally块不执行:
    (1)在finally语句块中发生了异常
    (2)在前面的代码中使用了System.exit()退出虚拟机
    (3)程序所在线程死亡
    (4)关闭cpu
    重点: 如果catch块或finally块中没有throw语句或者return语句,那么try…catch之后的语句就一定会执行,因为这个异常已经被正常捕获了。

    2. throw(s)处理异常
    如果当前出现的异常在本方法中无法处理,我们只能抛出异常。 如果每个方法都是简单的抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往回找,直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理(异常没有在本地处理,逻辑上throw之后的程序不会在进行)。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块(这属于异常没有得到处理,将终止出现异常的线程),将按照下面的步骤处理:
    (1)调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。
    (2)如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
    关于throw的用法我们有几点注意事项要注意:
    重点: throw语句后不允许有紧跟其他语句,因为这些没有机会执行(块外也不行,因为不会执行,无论是否被调用方捕获。如果异常是在本方法内部throw直接捕获,那是可以执行块后面的代码的,记住只要throw异常,throw之后的代码都不会在执行),以一段程序来说明这个问题:

    private void throwWay() {
        caexc();
    }
    public static void exc() throws ArithmeticException{
        int a =1;
        int b=4;
        for (int i=-2;i<3;i++){
            a=4/i;
            System.out.println("i="+i);
        }
    }
    public static void caexc(){
        try {
            exc();
        } catch (ArithmeticException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    

    执行结果如下:

    I/System.out: i=-2
                  i=-1
    W/System.err: java.lang.ArithmeticException: divide by zero
                      at com.tt.test.phototest.activity.ExceptionActivity.exc(ExceptionActivity.java:88)
                      at com.tt.test.phototest.activity.ExceptionActivity.caexc(ExceptionActivity.java:94)
    
    

    可以看到当 i 为0的时候抛出了异常,同时程序也不再往下执行,中断并打印异常信息

    异常被吃掉了

    正常的情况是我们在一个方法中抛出异常,在调用他的方法中捕获异常,并且异常的类型是相同的才可以正确捕获,例如两个都是IO异常,抛出和捕获的异常的类型不同也是不能正常捕获的,例如throw出的是IO异常,而在调用他的方法中捕获的是空指针异常,是不能正常捕获的,及时调用方捕获了也是属于java的原生异常捕获。最典型的吃掉异常就是在catch中不做任何处理,这样肯定是不行的,需要对捕获的异常进行正确处理,最简单的就是打印堆栈信息,下面是一种比较好的处理方式,可以参考:

    map.put("status", 500);   
    try{
        //代码省略
        map.put("message", "success!");   
        map.put("status", 200);   
    } catch (UnknownHostException e) {
        //域名错误
        map.put("message", "请输入正确的网址");
        LoggerUtils.fmtError(e, "网址异常[%s]", url);
    } catch (SocketTimeoutException e) {
        //超时
        map.put("message", "请求地址超时");
        LoggerUtils.fmtError(e, "请求地址超时[%s]", url);
     
    } catch (Exception e) {
        //其他异常
        map.put("message", "请求出现未知异常,请重试!\r\n" + e.getMessage());
        LoggerUtils.fmtError(e, "请求出现未知异常,请重试![%s]", url);
    }
    return map;
    

    或者我们继续将异常抛出也可以:

    try{
        //代码省略
    } catch (UnknownHostException e) {
        throw new HNException("xxx处理失败。",e);
    }
    

    注意点

    (1)catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么重新抛出新类型的异常
    (2)不要用try…catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况
    (3)避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常
    (4)细化异常的类型,不要不管什么类型的异常都写成Excetpion
    (5)不要捕获unchecked Exception
    (6)finally块不能抛出异常,如果抛出,外部捕捉到的异常就是finally块中的异常信息,而try,catch块中发生的真正的异常堆栈信息则丢失了
    (7)抛出自定义异常异常时带上原始异常信息
    (8)不要使用同时使用异常机制和返回值来进行异常处理

    尊重作者,尊重原创,参考文章:
    https://blog.csdn.net/zx64881926/article/details/52300271
    https://blog.csdn.net/csdnliuxin123524/article/details/78657118
    https://blog.csdn.net/jakezhang1990/article/details/72880700
    https://www.cnblogs.com/liuwt0911/articles/3730438.html
    https://www.jianshu.com/p/49d2c3975c56
    https://www.sojson.com/blog/251.html

    相关文章

      网友评论

          本文标题:java异常处理机制

          本文链接:https://www.haomeiwen.com/subject/nlgirctx.html