java异常处理机制

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常,那么异常发生之后怎么办,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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 2.JAVA异常 异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程...
    青城楼主阅读 551评论 0 0
  • 什么是异常? 异常本质上是程序上的错误,错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误。 编译...
    若兮缘阅读 3,339评论 0 11
  • 概念介绍 异常是发生在程序执行过程中阻碍程序正常执行的错误事件,当一个程序出现错误时,可能的情况有如下3种: 语法...
    niaoge2016阅读 5,170评论 2 20
  • 本文部分来自于:代码钢琴家blog address:www.cnblogs.com/lulipro/p/75042...
    八目朱勇铭阅读 1,316评论 0 4
  • 初识异常(Exception) 比如我们在取数组里面的某个值得时候,经常会出现定义的取值范围超过了数组的大小,那么...
    iDaniel阅读 1,867评论 1 2