Java异常处理

Java异常体系

体系结构

                                                                    Java异常类体系

说明

Throwable是所有异常的基类,它有两个子类:Error和Exception。

Error表示系统错误或资源耗尽,由Java系统自己使用,应用程序不应抛出和处理(如虚拟机错误:VirtualMachineError、内存溢出错误:OutOfMemoryError、栈溢出错误:StackOverflowError)。

Exception表示应用程序错误,它有很多子类,应用程序也可以通过继承Exception或其子类创建自定义异常,其三个直接子类:IOException(输入输出I/O异常)、RuntimeException(运行时异常)、SQLException(数据库SQL异常)。在Exception中RuntimeException比较特殊,属于非受检异常(unchecked exception),而其他Exception自身和其他子类则是受检异常(checked exception),另外,Error及其子类也是非受检异常。

checked和unchecked的区别在于Java如何处理这两种异常。对于checked异常,Java会强制程序员进行处理,否则会编译错误,而对于unchecked异常则没有这个要求。

常见RuntimeException

异常说明异常说明
NullPointerException空指针异常NumberFomatexception数字格式错误
IllegalStateException非法状态IndexOutOfBoundsExcpetion索引下标越界
ClassCastException非法强制类型转换ArrayIndexOutOfBoundsException数组索引越界
IllEgalArgumentException参数错误StringIndexOutOfBoundsException字符串索引越界

Throwable类有两个主要参数:一个是message,表示异常消息;另一个是cause,表示触发该异常的其他异常。异常可以形成一个异常链,上层的异常由底层触发,cause表示底层异常。

Throwable说明

构造方法:

public Throwable()
​
public Throwable(String message)
​
public Throwable(String message, Throwable cause)
​
public Throwable(Throwable cause)

Throwable类有两个主要参数:一个是message,表示异常消息;另一个是cause,表示触发该异常的其他异常。异常可以形成一个异常链,上层的异常由底层异常触发,cause表示底层异常。Throwable还有一个public方法用于设置cause:

Throwable initCause(Throwable cause)

Throwable的某些子类没有带cause参数的构造方法,就可以通过这个方法来设置,这个方法最多只能被调用一次。在所有构造方法的内部,都有一句重要的函数调用:

fillInStackTrace();

它会将一次栈信息保存下来,这是我们能看到异常栈的关键。

异常处理

处理的方式包括:catch、throw、finally、try-with-resources和throws

catch匹配

catch可以多条:

        try {
            // 可能异常代码
        } catch (NumberFormatException e) {
            System.out.println("not valid number " + e.getMessage());
        } catch (RuntimeException e) {
            System.out.println("runtime exception " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }

异常处理机制将根据抛出的异常类型找到第一个匹配的catch块,找到后,执行catch块内的代码,不再执行其他catch块,如果没有找到,会继续到上层方法中查找。需要注意的是,抛出的异常类型是catch中声明异常的子类放在前面,如果积累Exception放在前面,则其他更具体的catch代码将得不到执行。

其他:多个异常可用'|'拼接

        try {
            // 可能异常代码
        } catch (NumberFormatException | IndexOutOfBoundsException e) {
            System.out.println("not valid number " + e.getMessage());
        }

异常链 

对于NumberFormatException重新抛出了一个AppException,并且把当前的Exception e作为cause传递给了AppException,这样就形成了一个异常链,而在main方法中捕获了AppException就能获取到上一层的cause信息。

为什么要重新抛出呢?

因为当前代码不能完全处理该异常,需要调用者进一步处理。

finally

catch后面还可以跟finally语句

try{
    // 可能异常
} catch(Exception e){
    // 捕获异常
} finally{
    // 不管有无异常都执行
}

finally内的代码不管有无异常发生,都会执行,具体来说:

1. 如果没有异常发生,在try内的代码执行结束后执行
​
2. 如果有异常发生且被catch捕获,在catch内的代码执行结束后执行。
​
3. 如果有异常发生但没被捕获,则在异常被抛给上层之前执行

由于finally这个特性,它一般用于释放资源,如数据库连接、文件流等。

try/catch/finally语法中,catch不是必须的,也可以是try/finally:表示不捕获异常,异常自动向上传递,但finally中的代码在异常发生后也执行。

finally语句有一个执行细节:如果在try或者catch语句内有return语句,则return语句在finally语句执行结束后才执行,但finally并不能改变返回值:

public static int test1(){
        int res = 0;
        try {
            return res;
        } finally {
            res = 2;
        }
    }

这个函数test1返回值为0,而不是2。实际执行过程:在执行到try内的return res; 语句前,会先将返回值res保存在一个临时变量中,然后才执行finally语句,最后try再返回那个临时变量,finally中对res的修改不会被返回。

如果在finally中也有return语句呢?try和catch内的return会丢失,实际会返回finally中的返回值。finally中有return不仅会覆盖try和catch内的返回值,还会掩盖try和catch内的异常,就像异常没有发生一样,比如:

public static int test2(){
        int res = 0;
        try {
            int a = 5 / res;
            return res + 1;
        } finally {
            return res + 2;
        }
    }

这个函数test2的返回值是2,并且不会抛出异常,即原本要抛出ArithmeticException就会被覆盖掉。

    public static int test3() throws AppException {
        try {
            int a = 2 / 0;
        }finally {
            throw new AppException("finally 处理异常");
        }
    }

这个函数test3将会抛出AppException异常,异常信息为"finally 处理异常",覆盖了try中将要抛出的ArithmeticException异常

所以,一般而言,为了避免混淆,应该避免在finally中使用return语句或者抛出异常,如果调用的其他代码可能抛出异常,则应该捕获异常并进行处理。

如何使用异常

异常应该且仅用于异常情况

是指异常不能代替正常的条件判断,比如一个循环不能到到下标越界后再结束异常,应该先检查下标是否有效再进行处理。再如空指针异常,应该先判断是否为null再做处理。

另一方面,真正出现异常的时候,应该抛出异常来暴露问题,而不是返回特殊值。

异常处理的目标

异常大概可以分为三种来源:用户、程序员、第三方。用户是指用户的输入有问题;程序员是指编程错误;第三方泛指其他情况,如I/O错误、网络、数据库、第三方服务等。每种异常都应该进行适当的处理。

处理的目标可以分为恢复和报告。

恢复是指通过程序自动解决问题。报告的最终对象可能是用户,即程序使用者,也可能是系统运维人员或者程序员。报告的目的也是为了恢复,但这个恢复经常需要人的参与。

对用户:如果用户输入不对,可以提示用户具体哪里输入不对,如果是编程错误,可以提示用户系统错误、建议联系客服,如果是第三方连接问题,可以提示用户稍后重试。

对系统运维人员或程序员,他们一般不关心用户输入错误,而关注编程错误或第三方错误,对于这些错误,需要报告尽量完整的细节,包括异常链、异常栈等,以便尽快定位和解决问题。

异常处理的一般逻辑

如果自己知道怎么处理异常,就进行处理;如果可以通过程序自动解决,就自动解决;如果异常可以被自己解决,就不需要再向上报告。

如果自己不能完全解决,就应该向上报告。如果自己有额外信息可以提供,有助于分析和解决问题,就应该提供,可以以原异常为cause重新抛出一个异常。

总有一层代码需要为异常负责,可能是知道如何处理该异常的代码,可能是面对用户的代码,也可能是主程序。如果异常不能自动解决,对于用户,应该根据异常信息提供用户能理解和对用户有帮助的信息;对运维和开发人员,则应该输出详细的异常链和异常栈到日志。

这个逻辑与在公司中处理问题的逻辑是类似的,每个级别都有自己应该解决的问题,自己能处理的自己的处理,不能处理的就应该报告上级,把下级的告诉他的和他自己知道的一并告诉上级,最终,公司老板必须要为所有问题负责。每个级别既不应该掩盖问题,也不应该逃避责任。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值