• 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 {
     异常的出口
    }]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    我们发现一旦出现异常,程序就终止了"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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    我们发现,一旦 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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

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

    catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
     ...
    }
    
    • 1
    • 2
    • 3

    例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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    由于 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");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    无论是否存在异常,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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    例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]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    例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]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    可以看到,程序已经异常终止了,没有执行到 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

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

    异常说明

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

    public static int divide(int x, int y) throws ArithmeticException { 
     if (y == 0) { 
     throw new ArithmeticException("抛出除 0 异常"); 
     } 
     return x / y; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    关于 finally 的注意事项

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

    在这里插入图片描述

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

  • 相关阅读:
    从零开始,开发一个 Web Office 套件(6):光标 & Click 事件
    LeetCode 热题 HOT 100:回溯专题
    民安智库(第三方社会评估调研公司)不同客户满意度分析方法应用场景
    linux操作系统中的动静态库(未完)
    LeetCode515. Find Largest Value in Each Tree Row
    基于SpringBoot的线上买菜系统
    软件设计师--其他高频考点总结
    【vue】axios封装拦截
    自然语言处理(NLP)-spacy简介以及安装指南(语言库zh_core_web_sm)
    springboot和springcloud 和springcloud Alibaba的版本选择
  • 原文地址:https://blog.csdn.net/qq_56044032/article/details/127704689