• 7.0 异常处理


    1. 异常概述

    1.1. 异常的概念

    Java中的异常是指Java程序在运行时可能出现的错误或非正常情况,比如在程序中试图打开一个根本不存在的文件,在程序中除0等。异常是否出现,通常取决于程序的输入、程序中对象的当前状态以及程序所处的运行环境。程序抛出异常之后,会对异常进行处理。异常处理将会改变程序的控制流程,出于安全性考虑,同时避免异常程序影响到其他正常程序的运行,操作系统通常将出现异常的程序强行中止,并弹出系统错误提示。

    下面通过一个案例认识一下什么是异常,在本案例中,计算以0为除数的表达式,运行程序并观察程序的运行结果。

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. int result = divide(4, 0); // 调用divide()方法,第2个参数为0
    5. System.out.println(result);
    6. }
    7. /**
    8. * 下面的方法实现了两个整数相除
    9. * @param x
    10. * @param y
    11. * @return
    12. */
    13. public int divide(int x, int y) {
    14. int result = x / y; //定义一个变量result记录两个数相除的结果
    15. return result; //将结果返回
    16. }
    17. }

    运行代码,控制台显示的运行结果如下图所示。

    由上图可知,程序发生了算术异常(ArithmeticException),提示运算时出现了被0除的情况。异常发生后,程序会立即结束,无法继续向下执行。

    1.2. 异常类

    Java提供了大量的异常类,每一个异常类都表示一种预定义的异常,这些异常类都继承自java.lang包下的Throwable类。Throwable类的继承体系图如下所示。

    由上图中可知,Throwable类是所有异常类的父类,它有两个直接子类Error类和Exception类,其中,Error类代表程序中产生的错误,Exception类代表程序中产生的异常。

    (1)Error类

    Error类称为错误类,它表示Java程序运行时产生的系统内部错误或资源耗尽的错误,这类错误比较严重,仅靠修改程序本身是不能恢复执行的。例如,使用java命令去运行一个不存在的类就会出现Error错误。

    (2)Exception类

    Exception类称为异常类,它表示程序本身可以处理的错误,在Java程序中进行的异常处理,都是针对Exception类及其子类的。在Exception类的众多子类中有一个特殊的子类—RuntimeException类,RuntimeException类及其子类用于表示运行时异常。 Exception类的其他子类都用于表示编译时异常。

    (3)Throwable类的常用方法

    Throwable类中的常用方法,具体如下。

    方法声明

    功能描述

    String getMessage()

    返回异常的消息字符串

    String toString()

    返回异常的简单信息描述

    void printStackTrace()

    获取异常类名和异常信息,以及异常出现在程序中的位置,把信息输出在控制台。

    2. 运行时异常与编译时异常

    2.1. 编译时异常

    在实际开发中,经常会在程序编译时产生异常,这些异常必须要进行处理,否则程序无法正常运行,这种异常被称为编译时异常,也称为checked异常。在Exception类中,除了RuntimeException类及其子类,Exception的其他子类都是编译时异常。编译时异常的特点是Java编译器会对异常进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。

    处理编译时期的异常有两种方式,具体如下:

    • 使用try…catch语句对异常进行捕获处理。

    • 使用throws关键字声明抛出异常,调用者对异常进行处理。

    2.2. 运行时异常

    另一种异常是在程序运行时产生的,这种异常即使不编写异常处理代码,依然可以通过编译,因此被称为运行时异常,也称为unchecked异常。RuntimeException类及其子类都是运行时异常。运行时异常的特点是在程序运行时由Java虚拟机自动进行捕获处理的,Java编译器不会对异常进行检查。也就是说,当程序中出现这类异常时,即使没有使用try…catch语句捕获或使用throws关键字声明抛出,程序也能编译通过,只是程序在运行过程中可能报错。

    在Java中,常见的运行时异常有多种,具体如下所示。

    方法声明

    功能描述

    ArithmeticException

    算术异常

    IndexOutOfBoundsException

    索引越界异常

    ClassCastException

    类型转换异常

    NullPointerException

    空指针异常

    NumberFormatException

    数字格式化异常

    运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。例如,通过数组的索引访问数组的元素时,如果索引超过了数组范围,就会发生索引越界异常,代码如下所示:

    1. int[] arr=new int[5];
    2. System.out.println(arr[6]);

    在上面的代码中,由于数组arr的length为5,最大索引应为4,当使用arr[6]访问数组中的元素就会发生数组索引越界的异常。

    2.3. 运行时异常案例

    (1)算术异常

    ArithmeticException,算术异常,在做整数的除法或整数求余运算时可能产生的异常,它是在除数为零时产生的异常。

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. int num = 1 / 0;
    5. }
    6. }

    (2)数组下标越界异常

    ArrayIndexOutOfBoundsException,数组下标越界异常,当引用数组元素的下标超出范围时产生的异常。

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. int []num = new int[5];
    5. System.out.println(num[5]);
    6. }
    7. }

    (3)类型转换异常

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. ArrayList list = new ArrayList();
    5. list.add("1111");
    6. Integer num = (Integer) list.get(0);
    7. }
    8. }

    (4)空指针异常

    NullPointerException,空指针异常,即当某个对象的引用为null时调用该对象的方法或使用对象会产生该异类。

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. String name = null;
    5. System.out.println(name.length());
    6. }
    7. }

    (5)数字格式错误异常

    NumberFormatException,数字格式错误异常,在将字符串转换为数值时,如果字符串不能正确转换成数值则产生该异常。

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. Double d = Double.parseDouble("1.2m1");
    5. }
    6. }

    3. 异常处理

    在Java中,通过try、catch、finally、throw、throws这5个关键字进行异常对象的处理。具体说明如下所示。

    关键字

    功能描述

    try

    里面放置可能引发异常的代码

    catch

    后面对应异常类型和一个代码块,该关键字表明catch块是用于处理这种类型的代码块

    finally

    主要用于回收在try代码块里打开的物理资源,如数据库连接、网络连接和磁盘文件。异常机制保证finally块总是被执行

    throw

    用于抛出一个实际的异常。它可以单独作为语句来抛出一个具体的异常对象

    throws

    用在方法签名中,用于声明该方法可能抛出的异常

    3.1. try...catch语句

    为了使发生异常后的程序代码正常执行,程序需要捕获异常并进行处理,Java提供了try…catch语句用于捕获并处理异常。try…catch语句的语法格式如下所示:

    1. try{
    2. 代码块
    3. }catch(ExceptionType e){
    4. 代码块
    5. }

    (1)try...catch语句编写注意事项

    • try代码块是必需的。

    • catch代码块和finally代码块都是可选的,但catch代码块和finally代码块至少要出现一个。

    • catch代码块可以有多个,但捕获父类异常的catch代码块必须位于捕获子类异常的catch代码块后面。

    • catch代码块必须位于try代码块之后。

    (2)try...catch语句异常处理流程

    由上图可知,程序通过try语句捕获可能出现的异常,如果try语句没有捕获到异常,则直接跳出try…catch语句块执行其他程序;如果在try语句中捕获到了异常,则程序会自动跳转到catch语句中找到匹配的异常类型进行相应的处理。异常处理完毕,最后执行其他程序语句。

    (3)try...catch语句异常处理案例

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. try {
    5. int result = divide(4, 0); //调用divide()方法
    6. System.out.println(result);
    7. } catch (Exception e) { //对异常进行处理
    8. System.out.println("捕获的异常信息为:" + e.getMessage());
    9. }
    10. System.out.println("程序继续向下执行...");
    11. }
    12. /**
    13. * 下面的方法实现了两个整数相除
    14. * @param x
    15. * @param y
    16. * @return
    17. */
    18. public int divide(int x, int y) {
    19. int result = x / y; //定义一个变量result记录两个数相除的结果
    20. return result; //将结果返回
    21. }
    22. }

    3.2. finally语句

    在程序中,有时候会希望一些语句无论程序是否发生异常都要执行,这时就可以在try…catch语句后,加一个finally代码块。finally语句的语法格式如下所示:

    1. try{
    2. 代码块
    3. } catch(ExceptionType e){
    4. 代码块
    5. } finally{
    6. 代码块
    7. }

    注意:finally代码块必须位于所有catch代码块之后。

    (1)try…catch…finally语句的异常处理流程

    由上图可知,在try…catch…finally语句中,不管程序是否发生异常,finally代码块中的代码都会被执行。需要注意的是,如果程序发生异常但是没有被捕获到,在执行完finally代码块中的代码之后,程序会中断执行。

    (2)try…catch…finally语句的异常处理案例

    1. public class TestDemo {
    2. @Test
    3. public void test() {
    4. //下面的代码定义了一个try…catch…finally语句用于捕获异常
    5. try {
    6. int result = divide(4, 0); //调用divide()方法
    7. System.out.println(result);
    8. } catch (Exception e) { //对捕获到的异常进行处理
    9. System.out.println("捕获的异常信息为:" + e.getMessage());
    10. return; //用于结束当前语句
    11. } finally {
    12. System.out.println("进入finally代码块");
    13. }
    14. System.out.println("程序继续向下…");
    15. }
    16. /**
    17. * 下面的方法实现了两个整数相除
    18. *
    19. * @param x
    20. * @param y
    21. * @return
    22. */
    23. public int divide(int x, int y) {
    24. int result = x / y; //定义一个变量result记录两个数相除的结果
    25. return result; //将结果返回
    26. }
    27. }

    注意:如果在try...catch中执行了System.exit(0)语句,finally代码块不再执行。System.exit(0)表示退出当前的Java虚拟机,Java虚拟机停止了,任何代码都不能再执行了。

    4. 抛出异常

    方法运行过程中如果产生了异常,在这个方法中就生成一个代表该异常类的对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理该异常。这个过程称为抛出异常。

    4.1. throws关键字

    有时方法中产生的异常不需要在该方法中处理,可能需要由该方法的调用方法处理,这时可以在声明方法时用throws子句声明抛出异常,将异常传递给调用该方法的方法处理,调用者在调用方法时,就明确地知道该方法有异常,并且必须在程序中对异常进行处理,否则编译无法通过。

    使用throws关键字抛出异常的语法格式如下所示:

    1. 修饰符 返回值类型 方法名(参数) throws 异常类1,异常类2...{
    2. 方法体
    3. }

    (1)使用throws关键字抛出异常案例

    可以使用try…catch语句处理divide()方法抛出异常,也可以继续使用throws关键字将Exception抛出。

    1. public class TestDemo {
    2. @Test
    3. public void test(){
    4. //下面的代码定义了一个try…catch语句用于捕获异常
    5. try {
    6. int result = divide(4, 2); //调用divide()方法
    7. System.out.println(result);
    8. } catch (Exception e) { //对捕获到的异常进行处理
    9. e.printStackTrace(); //打印捕获的异常信息
    10. }
    11. }
    12. /**
    13. * 下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
    14. * @param x
    15. * @param y
    16. * @return
    17. * @throws Exception
    18. */
    19. public int divide(int x, int y) throws Exception {
    20. int result = x / y; //定义一个变量result记录两个数相除的结果
    21. return result; //将结果返回
    22. }
    23. }

    4.2. throw关键字

    在Java程序中,除了throws关键字,还可以使用throw关键字抛出异常。与throws关键字不同的是,throw关键字用于方法体内,抛出的是一个异常实例,并且每次只能抛出一个异常实例。

    使用throw关键字抛出异常的语法格式如下所示:

    throw ExceptionInstance;

    (1)使用throw关键字抛出异常案例

    1. public class TestDemo {
    2. @Test
    3. public void test(){
    4. // 下面的代码定义了一个try…catch语句用于捕获异常
    5. int age = -1;
    6. try {
    7. printAge(age);
    8. } catch (Exception e) { // 对捕获到的异常进行处理
    9. System.out.println("捕获的异常信息为:" + e.getMessage());
    10. }
    11. }
    12. /**
    13. * 定义printAge()输出年龄
    14. * @param age
    15. * @throws Exception
    16. */
    17. public void printAge(int age) throws Exception {
    18. if(age <= 0){
    19. // 对业务逻辑进行判断,当输入年龄为负数时抛出异常
    20. throw new Exception("输入的年龄有误,必须是正整数!");
    21. }else {
    22. System.out.println("此人年龄为:"+age);
    23. }
    24. }
    25. }

    5. 异常处理综合案例

    网页前端输入学生学号、年龄、姓名信息,进行保存。输入信息需满足,学号长度为4位、年龄大于0才可以保存成功,否则提示保存失败及失败原因。

    • Student类

    1. public class Student {
    2. private String no;
    3. private String name;
    4. private int age;
    5. public String getNo() {
    6. return no;
    7. }
    8. public void setNo(String no) {
    9. this.no = no;
    10. }
    11. public String getName() {
    12. return name;
    13. }
    14. public void setName(String name) {
    15. this.name = name;
    16. }
    17. public int getAge() {
    18. return age;
    19. }
    20. public void setAge(int age) {
    21. this.age = age;
    22. }
    23. @Override
    24. public String toString() {
    25. return "Student{" +
    26. "no='" + no + '\'' +
    27. ", name='" + name + '\'' +
    28. ", age=" + age +
    29. '}';
    30. }
    31. }
    • StudentController

    1. public class StudentController {
    2. /**
    3. * 接收前端输入信息
    4. * @param student
    5. */
    6. public void addStudent(Student student) throws Exception{
    7. StudentService service = new StudentService();
    8. service.saveStudent(student);
    9. }
    10. }

    • StudentService

    1. public class StudentService {
    2. /**
    3. * 保存学生信息
    4. * @param student
    5. * @throws Exception
    6. */
    7. public void saveStudent(Student student) throws Exception {
    8. //校验学号是否正确,学号为4位
    9. if(student.getNo() == null || student.getNo().length() != 4){
    10. throw new Exception("学号信息错误:" + student.getNo());
    11. }
    12. //校验学生年龄
    13. if(student.getAge() <= 0){
    14. throw new Exception("年龄信息错误:" + student.getAge());
    15. }
    16. System.out.println("保存学生信息成功,保存的学生信息是:" + student);
    17. }
    18. }
    • 测试类

    1. public class TestDemo {
    2. /**
    3. * 模拟前端输入学生信息保存
    4. */
    5. @Test
    6. public void test(){
    7. Student student = new Student();
    8. student.setNo("1001");
    9. student.setName("张三");
    10. student.setAge(20);
    11. StudentController controller = new StudentController();
    12. try{
    13. controller.addStudent(student);
    14. }catch (Exception e){
    15. System.out.println("保存学生信息失败,失败原因是:" + e.getMessage());
    16. }
    17. }
    18. }

    6. Debug调试

    Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。

    6.1. 设置断点

    在左边行号栏单击左键,或者快捷键Ctrl+F8打上/取消断点,断点行的颜色可自己去设置。

    6.2. 启动Debug模式

    以Debug模式启动服务,左边的一个按钮则是以Run模式启动。在开发中,我一般会直接启动Debug模式,方便随时调试代码。

    6.3. 调试按钮

    Step Over (F8):步过,一行一行地往下走,如果这一行上有方法不会进入方法。

    Step Into (F7):步入,如果当前行有方法,可以进入方法内部,一般用于进入自定义方法内,不会进入官方类库的方法,如第25行的put方法。

    Step Out (Shift + F8):步出,从步入的方法内退出到方法调用处,此时方法已执行完毕,只是还没有完成赋值。

    Resume Program (F9):恢复程序,比如,你在第20行和25行有两个断点,当前运行至第20行,按F9,则运行到下一个断点(即第25行),再按F9,则运行完整个流程,因为后面已经没有断点了。

  • 相关阅读:
    Python Requets库学习总结
    车牌号识别(低级版)
    【Linux】常用指令
    最优化:建模、算法与理论(典型优化问题
    Doris的分区表和分桶表
    ChatGPT人工智能:ai大模型应用开发源码搭建
    ElasticSearch查询工具类分享
    好玩的js特效
    [附源码]java毕业设计基于web场馆预约管理系统
    三次握手与四次挥的问题,怎么回答?
  • 原文地址:https://blog.csdn.net/lv_soso/article/details/134499602