12.异常

本章目标

  • 异常简介
  • 异常处理

本章内容

基于IEmployeeDao对应的实现类中,会有哪些常见出现?比如修改可能会出现空指针异常

一、异常简介

1、什么是异常

异常是应用程序在运行过程中出现的错误或非正常的意外情况,即虚拟机的通常操作中可能遇到的异常,是一种常见的运行错误。

常见情况:数组下标越界、空指针的访问、试图读取不存在的文件、数学除零等

完全不出错是不可能的,不要刻意回避,要多想意外发生后如何去处理它!

2、为什么用异常

运行时发生的错误如果没有异常处理机制,程序将会终止并使所有已分配资源的状态保持不变,这样会导致资源泄露。

异常处理机制由编译器强制执行

如:不能收回某个程序分配的内存,以供其它程序使用。

3、异常的分类

Java中每种异常都是使用一个Java类来表示

在Java语言中,异常可以看作是一个类,异常类的根是Throwable。Throwable是类库java.lang包中的一个类,并派生出Exception类和Error类两个子类

3.1、异常类的结构

Throwable
   |____Error
   |____Exception
         |____RuntimeException
         |____其它Exception

Error:Java运行系统中的内部错误以及资源耗尽的情况。

Exception:RuntimeException及其子类(编程导致的异常)、其它Exception类

3.2、Error体系

Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形。

应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。

如果出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。

所以,在进行程序设计时,应该更关注Exception体系。

3.3、Exception体系

Exception体系包括RuntimeException体系和其他非RuntimeException的体系 :

  • RuntimeException:该体系包括错误的类型转换、数组越界访问和试图访问空指针等等。如果出现RuntimeException,那么一定是程序员的错误

    例如,可以通过检查数组下标和数组边界来避免数组越界访问异常。

  • 其他非RuntimeException(编译期异常):这类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误

3.4、异常类的层次结构

二、异常处理

1、异常产生过程

异常的产生,大致经历以下几个阶段:

  1. Java程序在执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
  2. 当Java运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这一过程称为捕获(catch)异常。
  3. 如果Java运行时系统找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出

所以需要对异常进行处理,否则程序将立即终止,无法继续执行。

2、异常处理方式

对于异常有两种处理方法 :

  • 捕获异常:通过try … catch方法,catch子句中放置处理异常的语句。
  • 向上抛异常:在方法名后面加上throws Exception…, 方法本身只是抛出异常,由函数调用者来捕获异常。

3、try…catch

3.1、基本结构:

try{
   监控代码
}catch(异常类型){
   处理语句
}
  • catch后面小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
  • catch可以写多个,从上到下,必须遵守从小到大。
  • 建议catch的时候,精确的一个一个处理,有利于程序的调试

3.2、示例

在控制台输入员工年龄,如果输入的是非数字类型,会抛异常InputMismatchException类型不匹配

注:为了测试异常方便,我们直接在addEmployee,来接收数据,实际业务中addEmployee方法只用来接收传过来的对象,而不是在方法里接收这些数据。

public class EmployeeDaoImpl {

    public void addEmployee(Employee employee) {
        Scanner scanner = new Scanner(System.in);
        ……

        System.out.println("请输入员工年龄");
        try {
            int age = scanner.nextInt();
            employee.setAge(age);
        } catch (InputMismatchException e) {
            System.out.println("类别不匹配");
        }
        scanner.close();
    }

}

3.3、嵌套try-catch块

有时块的一部分导致一个错误,而块本身也可能导致一个错误,必须将一个异常嵌套在另一个异常处理机制中

从内层try语句寻找与异常匹配的catch语句,如无,检查下一个,从里到外,直到最后一个catch

在操作的同时,有可能还会出现空打针异常,

public class EmployeeDaoImpl {

    public void addEmployee(Employee employee) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int empId = scanner.nextInt();
        employee.setEmpId(empId);

        System.out.println("请输入员工姓名");
        String empName = scanner.next();
        employee.setEmpName(empName);

        System.out.println("请输入员工年龄");
        try {
            int age = scanner.nextInt();
            try {
                employee.setAge(age);
            } catch (NullPointerException ee) {
                System.out.println("空指针了");
            }

        } catch (InputMismatchException e) {
            System.out.println("类别不匹配");
        }
        scanner.close();
    }

}

3.4、多重 catch 块

一段代码可能会生成多个异常

当引发异常时,会按顺序来查看每个 catch 语句,并执行第一个类型与异常类型匹配的语句

执行其中的一条 catch 语句之后,其他的 catch 语句将被忽略

使用多重 catch 语句时,异常子类一定要位于异常父类之前 ,否则,控制权将永远传不到子类,将产生一个不能到达的代码,即产生错误

public class EmployeeDaoImpl {

    public void addEmployee(Employee employee) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int empId = scanner.nextInt();
        employee.setEmpId(empId);

        System.out.println("请输入员工姓名");
        String empName = scanner.next();
        employee.setEmpName(empName);

        System.out.println("请输入员工年龄");
        try {
            int age = scanner.nextInt();
            employee.setAge(age);

        } catch (InputMismatchException e) {
            System.out.println("类别不匹配");
        } catch (NullPointerException ee) {
            System.out.println("空指针了");
        }
        scanner.close();
    }

}

3.5、java8新特性

可以这样书写:

catch(FileNotFoundException | ArithmeticException | NullpointerException e) { … }

示例

public class EmployeeDaoImpl {

    public void addEmployee(Employee employee) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int empId = scanner.nextInt();
        employee.setEmpId(empId);

        System.out.println("请输入员工姓名");
        String empName = scanner.next();
        employee.setEmpName(empName);

        System.out.println("请输入员工年龄");
        try {
            int age = scanner.nextInt();
            employee.setAge(age);

        } catch (InputMismatchException |NullPointerException e) {
            System.out.println("出现异常");
        }
        scanner.close();
    }

}

4、finally 块

finally语句的主要作用是在try或catch转到其他部分前做的一些善后工作。比如:关闭打开的文件,释放链接、内存等系统资源。

有时需要清理代码,但程序已经停止运行,使用finally 块可使程序不管发生什么异常都将执行

4.1、finally 块作用

  • finally是异常处理语句结构的一部分,表示无论什么情况都要执行的模块。
  • finally语句的执行是一种无条件的操作,无论在哪种异常下都会执行,即使try或catch模块中有continue、return、break等关键字,或者是有throw语句,程序都会执行finally 语句。
  • 每个try语句至少有一个catch子句或finally子句

4.2、try、catch 和 finally 块的执行流程

4.3、示例

完整结构为try catch finally,也可以写成try catch,或try finally

    public void addEmployee(Employee employee) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int empId = scanner.nextInt();
        employee.setEmpId(empId);

        System.out.println("请输入员工姓名");
        String empName = scanner.next();
        employee.setEmpName(empName);

        System.out.println("请输入员工年龄");
        try {
            int age = scanner.nextInt();
            employee.setAge(age);

        } catch (InputMismatchException |NullPointerException e) {
            System.out.println("出现异常");
        }finally {
            System.out.println("无论如何都会执行,释放内存等善后工作");
        }
        scanner.close();
    }

5、throws关键字

throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。对大多数Exception子类来说,Java 编译器会强迫你声明在一个成员函数中抛出的异常的类型

抛非运行时异常,运行时异常一般是程序员的问题

5.1、 向上抛

public class EmployeeDaoImpl {

    public void queryById() throws ClassNotFoundException {
        Class.forName("");
    }

}

5.2、测试类中处理

public static void main(String[] args) {

        EmployeeDaoImpl dao = new EmployeeDaoImpl();
        try {
            dao.queryById();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

6、throw语句

throw关键字通常用在方法体中,并且抛出一个异常对象。

  • 程序在执行到throw语句时立即停止,它后面的语句都不执行。
  • 通过throw抛出异常后,如果想在上一级代码中来捕获并处理异常,则需要在抛出异常的方法中使用throws关键字在方法声明中指明要抛出的异常;
  • 如果要捕捉throw抛出的异常,则必须使用try—catch语句

本例子接合自定义异常展示

7、自定义异常

JavaAPI提供的内置异常不一定总能捕获程序中发生的所有错误。有时会需要创建用户自定义异常

7.1、格式

public class XXXExcepiton extends Exception | RuntimeException{
       提供无参数的构造方法
       或
       提供一个有参数的构造方法,可自动生成
}

注意: 1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类 2.自定义异常类,必须的继承Exception或者RuntimeException 3.继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch 4.继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)

7.2、自定义异常MyException

public class MyException extends Exception {

    private static final long serialVersionUID = 1L;
    public MyException() {
    }

    public MyException(String message) {
        super(message);// 把参数传递给Throwable的带String参数的构造方法
    }

}

7.3、案例 (贯穿项目相关)

如果用户输入的年龄小于18,则抛出自定义异常

public static void main(String[] args) throws MyException  {
    ……
    else if(opertor == 2) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int empId = scanner.nextInt();
        employee.setEmpId(empId);

        System.out.println("请输入员工姓名");
        String empName = scanner.next();
        employee.setEmpName(empName);

        System.out.println("请输入员工年龄");
        int age = scanner.nextInt();
        //年龄小于18抛出异常对象
        if(age<18) {
            throw new MyException("用户年龄不能小于18");
        }
        employee.setAge(age);
        scanner.close();
    }
}

注意,在方法上面也需要通过throws抛异常

7.4、测试类中调用

public class TestException {

    public static void main(String[] args) {
        Employee employee = new Employee();
        EmployeeDaoImpl dao = new EmployeeDaoImpl();
        try {
            dao.addEmployee(employee);
        } catch (MyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(employee);

    }

}

8、throw 和 throws总结

  • throws说明你有哪个可能,倾向
  • throw的话,那就是你把那个倾向变成真实的了同时:
  • throws出现在方法函数头;而throw出现在函数体;
  • throws表示出现异常的一种可能性,并不一定会发生这些异常;
  • throw则是抛出了异常,执行throw则一定抛出了某种异常;
  • 两者都是消极处理异常的方式(这里的消极并不是说这种)

思维导图

image

posted @ 2025-03-31 17:47  icui4cu  阅读(20)  评论(0)    收藏  举报