7.0 异常处理

1. 异常概述

1.1. 异常的概念

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

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

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

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

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

1.2. 异常类

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

7.0 异常处理_第1张图片

由上图中可知,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

数字格式化异常

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

int[] arr=new int[5]; 

System.out.println(arr[6]);

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

2.3. 运行时异常案例

(1)算术异常

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

public class TestDemo {
    @Test
    public void test() {
        int num = 1 / 0;
    }
}

(2)数组下标越界异常

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

public class TestDemo {
    @Test
    public void test() {
        int []num = new int[5];
        System.out.println(num[5]);
    }
}

(3)类型转换异常

public class TestDemo {
    @Test
    public void test() {
        ArrayList list = new ArrayList();
        list.add("1111");
        Integer num = (Integer) list.get(0);
    }
}

(4)空指针异常

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

public class TestDemo {
    @Test
    public void test() {
        String name = null;
        System.out.println(name.length());
    }
}

(5)数字格式错误异常

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

public class TestDemo {
    @Test
    public void test() {
        Double d = Double.parseDouble("1.2m1");
    }
}

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语句的语法格式如下所示:

try{
    代码块
}catch(ExceptionType e){
    代码块
}

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

  • try代码块是必需的。

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

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

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

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

7.0 异常处理_第2张图片

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

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

public class TestDemo {
    @Test
    public void test() {
        try {
            int result = divide(4, 0);    //调用divide()方法
            System.out.println(result);
        } catch (Exception e) {                 //对异常进行处理
            System.out.println("捕获的异常信息为:" + e.getMessage());
        }
        System.out.println("程序继续向下执行...");
    }
    /**
     * 下面的方法实现了两个整数相除
     * @param x
     * @param y
     * @return
     */
    public int divide(int x, int y) {
        int result = x / y;             //定义一个变量result记录两个数相除的结果
        return result;                 //将结果返回
    }
}

3.2. finally语句

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

try{
    代码块
} catch(ExceptionType e){
    代码块
}  finally{
    代码块
}

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

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

7.0 异常处理_第3张图片

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

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

public class TestDemo {
    @Test
    public void test() {
        //下面的代码定义了一个try…catch…finally语句用于捕获异常
        try {
            int result = divide(4, 0);       //调用divide()方法
            System.out.println(result);
        } catch (Exception e) {               //对捕获到的异常进行处理
            System.out.println("捕获的异常信息为:" + e.getMessage());
            return;                          //用于结束当前语句
        } finally {
            System.out.println("进入finally代码块");
        }
        System.out.println("程序继续向下…");
    }
    /**
     * 下面的方法实现了两个整数相除
     *
     * @param x
     * @param y
     * @return
     */
    public int divide(int x, int y) {
        int result = x / y;             //定义一个变量result记录两个数相除的结果
        return result;                  //将结果返回
    }
}

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

4. 抛出异常

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

4.1. throws关键字

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

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

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

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

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

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

4.2. throw关键字

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

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

throw ExceptionInstance;

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

public class TestDemo {
    @Test
    public void test(){
        // 下面的代码定义了一个try…catch语句用于捕获异常
        int age = -1;
        try {
            printAge(age);
        } catch (Exception e) {  // 对捕获到的异常进行处理
            System.out.println("捕获的异常信息为:" + e.getMessage());
        }

    }
    /**
     * 定义printAge()输出年龄
     * @param age
     * @throws Exception
     */
    public void printAge(int age) throws Exception {
        if(age <= 0){
            // 对业务逻辑进行判断,当输入年龄为负数时抛出异常
            throw new Exception("输入的年龄有误,必须是正整数!");
        }else {
            System.out.println("此人年龄为:"+age);
        }
    }
}

5. 异常处理综合案例

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

  • Student类

public class Student {

    private String no;
    private String name;
    private int age;

    public String getNo() {
        return no;
    }

    public void setNo(String no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "no='" + no + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • StudentController

public class StudentController {

    /**
     * 接收前端输入信息
     * @param student
     */
    public void addStudent(Student student) throws Exception{
        StudentService service = new StudentService();
        service.saveStudent(student);
    }

}

  • StudentService

public class StudentService {

    /**
     * 保存学生信息
     * @param student
     * @throws Exception
     */
    public void saveStudent(Student student) throws Exception {
        //校验学号是否正确,学号为4位
        if(student.getNo() == null || student.getNo().length() != 4){
            throw new Exception("学号信息错误:" + student.getNo());
        }
        //校验学生年龄
        if(student.getAge() <= 0){
            throw new Exception("年龄信息错误:" + student.getAge());
        }
        System.out.println("保存学生信息成功,保存的学生信息是:" + student);
    }

}
  • 测试类

public class TestDemo {

    /**
     * 模拟前端输入学生信息保存
     */
    @Test
    public void test(){
        Student student = new Student();
        student.setNo("1001");
        student.setName("张三");
        student.setAge(20);
        StudentController controller = new StudentController();
        try{
            controller.addStudent(student);
        }catch (Exception e){
            System.out.println("保存学生信息失败,失败原因是:" + e.getMessage());
        }
    }

}

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,则运行完整个流程,因为后面已经没有断点了。

你可能感兴趣的:(Java程序设计,java,异常,异常处理)