Java基础系列-Exception异常处理


原创文章,转载请标注出处:《Java基础系列-Exception异常处理》


一、概述

Java代码中的异常处理是非常重要的一环,从代码中可以看到,它的使用已经和业务逻辑紧密的结合在一起,部分业务逻辑还是依靠异常来完成的,更多的时候进行异常处理可以完善逻辑,避免可能的出错,规避小错误引发的大停顿。

在一般的项目之中,都会自定义运行时异常,用以适应项目的需要,这种异常可被捕捉,也可不被捕捉,它们不会导致整个系统挂掉,但是很多情况下,不捕捉处理就会导致业务出错。

在这里我们模拟几种情况,点明异常捕捉的使用时机。

二、情况分析

先来看没有任何处理的代码:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
        
    }
    
    public static void invoke(){
        System.out.println("---11---");
        int i = 1/0;
        System.out.println("---12---");
    }
}

其执行结果如下:

---1---
---11---
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.donghao.test1.ExceptionTests01.invoke(ExceptionTests01.java:14)
    at com.donghao.test1.ExceptionTests01.main(ExceptionTests01.java:7)

解析:main方法调用invoke方法,在执行到第12行时出错,产生算法异常,此时由于无任何异常处理手段,结果就是,程序执行到这里之后直接中断,执行结果中输出的异常堆栈信息是Java内部默认的异常处理机制处理的结果。

改造一:我们在invoke方法内部加上异常捕捉机制,代码如下:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
        
    }
    
    public static void invoke(){
        try{
            System.out.println("---11---");
            int i = 1/0;
        }catch(Exception e){
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

执行结果:

---1---
---11---
---12---
---13---
---2---

结果解析:我们在invoke方法的执行代码外围添加异常捕捉代码,捕捉Exception异常,这是所有异常的基类,当然也包含这里的算法异常,那么这个捕捉机制就会将1/0产生的异常捕捉到,捕捉到这个异常之后,就会跳转到catch语句块中执行针对这个异常的处理语句,执行完成后,会继续执行try...catch语句块之后的代码,这样的好处显而易见,一处的小错误并不会阻挡整个代码的持续执行,当然如果是严重问题,我们确实需要暂停执行的,就不能使用这种情况,使用之前的代码就行,所以异常处理机制的执行时机完全是由项目的业务情况而定的,是非常灵活的,不是固定的死板的。我们要根据实际的业务场景来合理的使用才是正理。

改造二:我们在main方法中也添加异常捕捉,代码如下:

public class ExceptionTests01 {

    public static void main(String[] args) {
        try{
            System.out.println("---1---");
            invoke();
        }catch(Exception e){
            System.out.println("---2---");
        }
        System.out.println("---3---");
    }
    
    public static void invoke(){
        try{
            System.out.println("---11---");
            int i = 1/0;
        }catch(Exception e){
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

其执行结果如下:

---1---
---11---
---12---
---13---
---3---

结果几乎与之前的完全一致,不同之处在于2没有输出,一是我改变了2的输出位置,并新增了3的输出,现在3相当于之前2的位置,2没有输出的原因是因为任何一个异常只能被捕捉一次,一旦被捕捉处理,那么之后就不会再次被捕捉,即使我在main方法中将异常类型改成算法异常,也不会捕捉到,异常只会被距离它最近的包含该异常的异常捕捉到,这里的两个异常捕捉其实就是一个嵌套的异常捕捉,而且二者捕捉的异常还是一致的,一般情况我们是不会这么使用的,因为毫无意义。但不是说它就完全不会出现,可能invoke中的代码较长,会有多处异常情况出现,我们可以在main方法中统一捕捉,而invoke中的异常捕捉只针对单一异常,表示这个异常的出现不会影响invoke方法后面的代码执行,没有异常捕捉的代码一旦出现异常就会中断其后方所有代码的执行(同一代码块内),这个异常会被main方法中的异常捕捉机制捕捉到并执行处理,这样main方法中调用invoke之后的代码仍然可以执行,不会被调用发生异常而中断。

但是如果我们再将invoke方法中的异常捕捉改变如下:

public class ExceptionTests01 {

    public static void main(String[] args) {
        try{
            System.out.println("---1---");
            invoke();
        }catch(Exception e){
            System.out.println("---2---");
        }
        System.out.println("---3---");
    }
    
    public static void invoke(){
        try{
            System.out.println("---11---");
            int i = 1/0;
        }catch(NullPointerException e){
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

执行结果发生了变化:

---1---
---11---
---2---
---3---

为什么呢?正是因为我们更改了invoke方法中捕捉的异常类型,之前是异常基类型Exception,现在改成具体的空指针异常,那么这个异常捕捉就只能捕捉空指针异常,它对此处发生的算法异常就会视而不见(由于异常类型的不对口,那么这个异常捕捉相当于没有添加,可以想象成没有异常捕捉的情况),这样就导致invoke方法中在1/0发生异常之后的所有代码全部不会执行,而我们在main方法中新增的异常捕获却能捕获到这种算法异常,所以12和13都不会输出,而是在异常发生后直接就跳转到main方法中进行异常捕捉,执行catch语句块处理语句输出2,然后是3。

再看一个特殊的情况:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        for(int i = -2;i < 3;i++){
            System.out.println("---11---");
            System.out.println("12/"+i+"="+12/i);
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

invoke方法中是一个循环输出,当第12行发生异常时,循环中断,默认的异常处理机制打印异常堆栈:

---1---
---11---
12/-2=-6
---12---
---11---
12/-1=-12
---12---
---11---
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.donghao.test1.ExceptionTests01.invoke(ExceptionTests01.java:14)
    at com.donghao.test1.ExceptionTests01.main(ExceptionTests01.java:7)

改造一:在invoke方法的for循环外部添加try...catch:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        try{
            for(int i = -2;i < 3;i++){
                System.out.println("---11---");
                System.out.println("12/"+i+"="+12/i);
                System.out.println("---12---");
            }
            System.out.println("---13---");
        }catch(Exception e){
            System.out.println("---14---");
        }
        System.out.println("---15---");
    }
}

结果:

---1---
---11---
12/-2=-6
---12---
---11---
12/-1=-12
---12---
---11---
---14---
---15---
---2---

查看结果,发现循环还是中断了,当i=0时,第13行产生异常,之后循环中断,然后异常才会被for循环之外的异常捕捉到,这种场景也会在实际项目中出现,但不多见,具体场景为,针对循环进行异常捕捉,一旦循环中某一环产生异常,则整个循环终止,处理异常。

改造二:在循环体中加入try...catch块:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        for(int i = -2;i < 3;i++){
            try{
                System.out.println("---11---");
                System.out.println("12/"+i+"="+12/i);
                System.out.println("---12---");
            }catch(Exception e){
                System.out.println("---13---");
            }
            System.out.println("---14---");
        }
        System.out.println("---15---");
    }
}

结果为:

---1---
---11---
12/-2=-6
---12---
---14---
---11---
12/-1=-12
---12---
---14---
---11---
---13---
---14---
---11---
12/1=12
---12---
---14---
---11---
12/2=6
---12---
---14---
---15---
---2---

这种情况比较多见,我们将异常捕捉内置到for循环内部,只针对循环体进行异常捕捉,这样当某一次循环体执行时产生了异常,也能私下处理好,不会影响整个循环的继续执行。在循环中还可以结合continue和break关键字进行更加复杂的关系控制,来达到特定的业务需求。

这里我来展示一种情况,也是刚刚做过的一个业务场景:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        for(int i = -2;i < 3;i++){
            try{
                System.out.println("---11---");
                System.out.println("12/"+i+"="+12/i);
                System.out.println("---12---");
            }catch(Exception e){
                System.out.println("---13---");
                continue;
            }
            System.out.println("---14---");
        }
        System.out.println("---15---");
    }
}

你没看错,只是添加了一个continue;控制信息的展示,当发生异常之后,执行catch块代码,输出13后,不再执行输出14的操作,而是直接开始新的循环。

总结

  1. 异常的捕捉是有方向性和类型针对性的,异常会被距离异常发生点最近的包含或者就是捕捉该类型异常的捕捉点捕捉到。这样我们在做嵌套异常捕捉和多异常捕捉时,就一定要注意要将小范围的异常类型放置到靠进try块的位置,避免大类型劫持异常,导致你设置的异常类型无法生效。
  2. 我们将一段代码try...catch包裹,就可以将这段代码从这个方法体中隔离出来,将其影响度降到最低,即使其发生异常,也不会影响到后续代码的执行。
  3. throw关键字的配合使用,我们可以在catch块中使用,表示将捕捉到的异常再次抛出,这里不做处理,这样就必须在方法的调用处再次进行捕捉,可以持续抛出,但是直到最终的方法时,必须要进行处理(对应明确抛出的异常一定要进行捕捉处理,不论你抛几次)。

三、项目中的异常

异常的转换

项目中我们都会自定义异常,这些异常一般带有较为明确的目的,甚至我们可能会在项目中的每一层级定义不同的异常,这时候就会涉及到异常的转换,其实转换很简单,只要将异常进行捕捉,在catch块中将一行捕捉住,并重新抛出(throw)一个新的异常,将之前的异常信息e作为新异常的参数。

try{
    int i = 1/0;   
}catch(Exception e){
    throw new RuntimeException(e);
}

如上面的例子中,第2行会抛出一个异常,该异常将会被catch块捕捉到,然后内部消化,重新抛出一个RuntimeException,并将原来的异常信息作为新异常的异常信息(即保留原异常信息)。

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