谈谈 Java 异常

这是我参与更文挑战的第26天,活动详情查看: 更文挑战

前言

今天,来谈谈 程序编写中的 Exception 和 Error ,这些错误在我们开始写代码之后就一直如影随形的纠缠着我们,java 语言中提供了相对完善的异常处理机制,接下来,我们把异常和错误一次性搞明白。

一、异常概述

1.1 入门小故事

我们通过一个故事来看看几种情况,分别来了解异常和错误的概念。

假设在一个风和日丽的天气,我打算去理发,于是我准备去找托尼老师……

  • 1、我走到托尼老师的店门口,发现理发店门上贴着 “ 本店倒闭,暂停营业 ”。那么这对我来说我一点办法都没有,我的理发计划只能泡汤了。(这是严重的错误)
  • 2、假设理发店的托尼老师人在隔壁花店撩小妹妹,我去把他叫了回了,这样我的理发计划可以继续实现了。(这种我能在事发前处理的事,对应编译期异常。)
  • 2、假设托尼老师给我开始理发了,我让他来个碎发,结果他给我来了碗 3 mm 的寸头。(这种我在事发前不能预料的,对应运行时异常。)

1.2 基础概念

看完上面的小故事,我们也大概知道异常和错误的区别了,基础概念如下:

  • Error: 对应情况 1,一般是jvm发生错误,stackOverlowErrot,outOfMemeryError,一般程序写的有问题,确实内存不够用。
  • Exception
    • 编译期异常:对应情况2,这块我们应该进行异常处理的,否则程序无法通过编译。
    • 运行时异常:对应情况3,RuntimeException 及其子类,一般是程序的逻辑有问题,不推荐进行异常处理,而应该修改代码逻辑。

1.3 异常分类

异常和错误它们都继承 Throwable 类,它是 Java 语言中所有错误或异常的超类,只有当对象是此类或其子类时,才能通过 JVM 虚拟机或 Java throw 语句抛出来。

【注解】:

    1. 粉红色的是受检查的异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.受检查的异常必须在编译时被捕捉处理,命名为 Checked Exception 是因为Java编译器要进行检查,Java虚拟机也要进行检查,以确保这个规则得到遵守.
    1. 绿色的异常是运行时异常(runtime exceptions),需要程序员自己分析代码决定是否捕获和处理,比如 空指针,被0除…
    1. 而声明为Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕捉。

img

二、异常处理

2.1 处理方式

前面说过 Throwable 的分为 error 和 Exception 两大类,在它们发生时,应该怎么处理它们呢?

  • 1、JVM 的默认处理 : 我们这种情况很常见,jvm会先打印出错误的线程名称和异常的类名,异常的原因,打印出异常发生的位置,然后终止程序。
  • 2、try_catch_finally 处理,进行异常捕获
  • 3、throw 把问题抛给别人

【说明】:

​ 默认的处理方式说白了就是不处理,抛出异常信息,然后直接终止程序,不可以,通常需要用 try – catch 来处理,如果实在处理不了,就用throw 来抛出异常 交给它的上一级来处理,程序便不往下执行了,区别就i是try-catch处理完会进行执行后面的程序,而throw 则不会执行。

2.2 处理流程

try_catch_finally 标准格式:

try{
可能发生错误的代码    
}
 catch(异常类名 异常对象名){
     对异常处理
 }
 finally{
     释放资源
 }
复制代码

标准流程

  1. 先执行try语句块里面的代码

  2. 如果没有发生错误,执行finally语句块里面的代码,执行后面的代码

  3. 如果发生了错误,JVN会把异常封装成对象,然后和catch语句进行匹配,执行对应的catch语句,执行finally语句块里面的代码,执行后面的代码

  4. 先catch 小异常,在catch 大异常,注意书写顺序

2.3 知识扩展

下面开始对一些实践中的用例进行分析

第一个

try {
 // 业务代码
 // …
 Thread.sleep(1000L);
} catch (Exception e) {
 // Ignore it
}
复制代码

这段程序虽然短,但是违背了异常处理的两个原则

  • 1、尽量不要捕获类似Exception 这样的通用异常,而应该捕获具体异常,例如此块超时抛出的 InterruptedException,这样保证程序不会捕获到我们不希望捕获的异常。
  • 2、不要忽略异常,在catch 里面需要抛出异常 或者把异常输出到日志然后处理,否则,没人知道异常情况,可能会带来不可理解的错误

第二个:

try {
 // 业务代码
 // …
} catch (IOException e) {
e.printStackTrace();
}
复制代码

这块代码在是开发环境是没有问题的,但是 printStackTrace 不是个好的输出方式,如果是分布式系统,就得去找输入信息输出到哪里去了,因此,还是需要记录到日志中。

三、面试问题

3.1 final,finally,finalize的区别?

  • final: 最终的

    • 修饰类:该类不能被继承

    • 修饰方法:该方法不能被重写

    • 修饰变量:自定义常量

  • finally:

  • finally子句用于释放资源的,放在finally中代码一定会被执行(执行finally语句之前,JVM退出)

  • finalize:

    • Object类中的方法。用于垃圾回收

3.2 throw和throws的区别

throw:

  • 方法体中,后面跟的是异常对象名,并且只能抛出一个异常

  • 表示抛出异常,由方法体内的语句来处理

  • throw抛出的是一个异常对象说明这里肯定有一个异常产生了

throw new Exception();

throws:

  • 方法声明上,后面跟的是异常的类名,可以是多个异常类名,用逗号隔开

  • 表示可能抛出异常,异常由该方法的调用者来处理

  • throws是声明方法有异常,是一种可能性,这个异常并不一定会产生

3.3 如果在catch里面有return,请问finally还执行吗?如果执行,在return前还是后?

​ 答:finally 的执行应该在return 的前面。

下面通过一个例子来看看:

    int  init(){
        int a=10;
        System.out.println(a);
        try{
            System.out.println(a/0);
            a=20;
            System.out.println("发生异常,此时a="+a);
        }catch (ArithmeticException e){
            a=30;
            System.out.println("我处理异常,此时a="+a);
            return a;
        
        }finally {
            a=40;
            System.out.println("我是finally,此时a="+a);
        }
        System.out.println("最终的a值:"+a);
        return a;
    }
复制代码
    @Override
    public void run(String... args) throws Exception {
        System.out.println("最终的值:"+this.init());
    }

    int  init(){
        int a=10;
        System.out.println(a);
        try{
            System.out.println(a/0);
            a=20;
            System.out.println("发生异常,此时a="+a);
        }catch (ArithmeticException e){
            a=30;
            System.out.println("我处理异常,此时a="+a);
            return a;
             /*
			 * return a在程序执行到这一步的时候,这里不是return a而是return 30;这个返回路径就形成了。
			 * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
			 * 再次回到以前的返回路径,继续走return 30;
			 */
        }finally {
            a=40;
            System.out.println("我是finally,此时a="+a);
        }
            return a;
    }
复制代码
我处理异常,此时a=30
我是finally,此时a=40
最终的值:30
复制代码

可以发现,此时 a的值为30,说明,catch 中 的return 的时候,会继续执行finally 的语句,并且在return 前执行

参考资料:

  • 《Java 核心技术36讲》
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享