java中的异常

前言

之前学过C++的异常,现在又学Java中的异常,大同小异

1、异常机制

在说异常之前,我有必要回顾一下之前学的Linux中的信号。当一个进程意外终止,一定是收到了某个信号。而收到了信号的处理动作有三种:
1.忽略该信号
2.执行默认的处理动作(一般都是终止该进程)
3.执行自定义动作(由程序员自己决定到底执行什么)

个人认为,无论是C++还是Java的异常机制,都是对底层信号的封装。

异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。

程序的错误分为三种:

  1. 编译时错误:编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置(红色波浪下划线)
  2. 运行时错误:程序在执行过程中,遇到了无法执行的指令或操作,例如空指针异常,数组越界
  3. 逻辑错误:程序没有按照预期的逻辑顺序执行

2、异常结构

在这里插入图片描述

Throwable:是异常体系的顶层类,其派生出两个重要的子类,Error(错误)Exception(异常),二者都是 Java 异常处理的重要子类。

Error 和 Exception的区别

Error:是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError
在这里插入图片描述

这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

Exception:是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,空指针异常、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Exception又分为两类:一类是受查异常,另一类是非受查异常

非受查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。RuntimeException表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException

受查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。除了Exception中的RuntimeException及RuntimeException的子类以外,其他的Exception类及其子类(例如:IOException和ClassNotFoundException)都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

3、异常的处理机制

在 Java 应用程序中,异常处理机制为:抛出异常,捕获异常。
1. 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
2. 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止

对于错误、运行时异常、受查异常,Java技术所要求的异常处理方式有所不同。

  1. 错误:对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
  2. 运行时异常:由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
  3. 受查异常:对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。

能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时 系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。

4、异常的基本语法及其使用

try{ 
 有可能出现异常的语句 ; 
}[catch (异常类型 异常对象) {
} ... ]
[finally {
 异常的出口
}]

try 代码块中放的是可能出现异常的代码
catch 代码块中放的是出现异常后的处理行为
finally 代码块中的代码用于处理善后工作。会在最后执行
其中 catch 和 finally 都可以根据情况选择加或者不加

例1:不处理异常

public class Test8 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println("before");
        System.out.println(arr[100]);
        System.out.println("after");
    }
}

在这里插入图片描述
我们发现一旦出现异常,程序就终止了"after"没有正确输出


例2:使用 try catch 后的程序执行过程

public class Test8 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            // 打印出现异常的调用栈
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述

我们发现,一旦 try 中出现异常,那么 try 代码块中的程序就不会继续执行,而是交给 catch 中的代码来执行。catch 执行完毕会继续往下执行。
虽然这里的打印顺序不同,但是程序一定是先catch,再System.out.println(“after try catch”)

关于异常的处理方式

异常的种类有很多,我们要根据不同的业务场景来决定
对于比较严重的问题(例如和算钱相关的场景),应该让程序直接崩溃,防止造成更严重的后果
对于不太严重的问题(大多数场景),可以记录错误日志,并通过监控报警程序及时通知程序员
对于可能会恢复的问题(和网络相关的场景),可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息

关于 “调用栈”
方法之间是存在相互调用关系的,这种调用关系我们可以用 “调用栈” 来描述。在 JVM 中有一块内存空间称为 “虚拟机栈” 专门存储方法之间的调用关系。当代码中出现异常的时候,我们就可以使用 e.printStackTrace()。的方式查看出现异常代码的调用栈

例3: catch 只能处理对应种类的异常
修改了代码,让代码抛出的是空指针异常

public class Test8 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述

catch 语句不能捕获到刚才的空指针异常,因为异常类型不匹配

例4:catch 可以有多个

public class Test8 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("这是个数组下标越界异常");
            e.printStackTrace();
        } catch (NullPointerException e) {
            System.out.println("这是个空指针异常");
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述
一段代码可能会抛出多种不同的异常,不同的异常有不同的处理方式,因此可以搭配多个 catch 代码块

如果多个异常的处理方式是完全相同,也可以写成这样

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
 ...
}

例5:可以用一个 catch 捕获所有异常(不推荐)

public class Test8 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述

由于 Exception 类是所有异常类的父类,因此可以用这个类型表示捕捉所有异常
注意:catch 进行类型匹配的时候,不光会匹配相同类型的异常对象,也会捕捉目标异常类型的子类对象
如刚才的代码,NullPointerException 和 ArrayIndexOutOfBoundsException 都是 Exception 的子类,因此都能被捕获到

例6:finally 表示最后的善后工作, 例如释放资源

public class Test8 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("finally code");
        }
    }
}

在这里插入图片描述
无论是否存在异常,finally 中的代码一定都会执行到,保证最终一定会执行到 Scanner 的 close 方法

例7:使用 try 负责回收资源

try (Scanner sc = new Scanner(System.in)) {
    int num = sc.nextInt();
    System.out.println("num = " + num);
} catch (Exception e) {
    e.printStackTrace();
}

例6可以有一种等价写法, 将 Scanner 对象在 try 的 ( ) 中创建, 就能保证在 try 执行完毕后自动调用 Scanner的 close 方法

例8:如果本方法中没有合适的处理异常的方式,就会沿着调用栈向上传递

public class Test8 {
    public static void main(String[] args) {
        try {
            func();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
    public static void func() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
}

在这里插入图片描述

例9:如果向上一直传递都没有合适的方法处理异常,最终就会交给 JVM 处理,程序就会异常终止

public class Test8 {
    public static void main(String[] args) {
        func();
        System.out.println("after try catch");
    }
    public static void func() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
}

在这里插入图片描述

可以看到,程序已经异常终止了,没有执行到 System.out.println(“after try catch”); 这一行


**抛出异常** 除了 Java 内置的类会抛出一些异常之外,程序猿也可以手动抛出某个异常,使用 throw 关键字完成这个操作
public class Test8 {
    public static void main(String[] args) {
        System.out.println(divide(10, 0));
    }
    public static int divide(int x, int y) {
        if (y == 0) {
            throw new ArithmeticException("抛出除 0 异常");
        }
        return x / y;
    }
}

在这里插入图片描述

在这个代码中,我们可以根据实际情况来抛出需要的异常,在构造异常对象同时可以指定一些描述性信息

异常说明

我们在处理异常的时候,通常希望知道这段代码中究竟会出现哪些可能的异常
可以使用 throws 关键字,把可能抛出的异常显式的标注在方法定义的位置,从而提醒调用者要注意捕获这些异常

public static int divide(int x, int y) throws ArithmeticException { 
 if (y == 0) { 
 throw new ArithmeticException("抛出除 0 异常"); 
 } 
 return x / y; 
}

关于 finally 的注意事项

finally 中的代码保证一定会执行到,这也会带来一些麻烦

在这里插入图片描述

finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally)。但是如果finally 中也存在 return 语句,那么就会执行 finally 中的 return,从而不会执行到 try 中原有的 return
一般我们不建议在 finally 中写 return

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值