跳到主要内容

第18章-异常处理

提问:我们已经见过的异常有哪些?产生异常后,程序还能继续执行吗?

:

  1. 已经见过的异常有:ArrayIndexOutOfBoundsException、StackOverflowError、NullPointerException、ClassCastException等
  2. 产生异常后,程序将崩溃,不再执行后续代码,也不能继续提供服务;所以,如果不对异常进行处理,对程序来说,是致命的

概述

  • 程序中,因为各种原因,通常会产生各种异常,有基础类抛出的,也有自己代码中产生的

  • 异常会导致程序奔溃,后果极其严重,不可接受

  • 在程序的开发阶段,必须尽量的对可预见的异常进行捕获、处理,保证程序的运行

  • 好的异常处理机制,可以让异常处理代码和正常业务逻辑分离,保证程序健壮性、容错性、优雅性

  • 通常处理方式有:

    • 可能产生异常的代码中开发者自己处理
    • 抛出异常,给代码所在方法的调用者处理
  • 应用实例

    • 应用实例1,产生几种常见类型异常

      package com.bjpowernode.demo;

      /**
      * 异常示例
      */
      public class ExceptionDemo {
      public static void main(String[] args) {
      //产生异常
      //空指针异常
      String str=null;
      System.out.println(str.length());
      //数组越界异常
      int[] datas = new int[0];
      datas[0]=2;
      //被0除异常
      int result = 5/0;


      //异常后的逻辑,由于前面异常将导致程序崩溃,此处代码将不会被执行
      System.out.println("异常后的代码块。");
      }
      }

【练习】

  1. 练习演示的内容,熟悉异常类对程序的影响

异常处理机制

关键字

异常处理,主要由如下几个关键字或关键字组成的代码块完成

  • 自己处理,主要由下面3个关键字配合进行
    • try:可能产生异常的代码块;必选
    • catch:异常处理代码块,可以有1~n个catch,会根据try代码块中抛出的异常类型,自动匹配不同catch的异常处理代码块;必选
    • finally:后置处理代码块,用于资源回收,不管产生异常与否,都被执行;一般用来做收尾工作,像资源回收,如数据库的连接释放、文件io资源释放..;可选
  • 调用者处理,主要由下面2个关键字配合进行
    • throw:主动抛出异常类的对象,表示自己不处理,交给方法的调用者处理
    • throws:标识方法抛出的编译期异常,让调用方知道,调用此方法需要处理哪些异常

自己处理

  • 对于一段代码,如果预计会产生异常,一般需要使用try代码块进行异常捕获,并进行处理

  • 并接着定义catch捕获到的不同类型异常的处理代码

  • 如果有资源需要进行回收,接着在finally中定义后置处理代码块

  • 语法规则:

    • 方式1、不带finally

      try{
      ...//可能产生异常的代码块
      }catch(异常类型1 异常的变量名1){
      ...//异常类型1处理代码块
      }catch(异常类型2 异常的变量名2){
      ...//异常类型2处理代码块
      }
    • 方式2、带finally

      try{
      ...//可能产生异常的代码块
      }catch(异常类型1 异常的变量名1){
      ...//异常类型1处理代码块
      }catch(异常类型2 异常的变量名2){
      ...//异常类型2处理代码块
      }finally{
      ...//后置处理代码块
      }
  • 注意,多个catch捕获异常时:

    • 只要有一个与产生的异常类型匹配,就不会去匹配下一个catch,类似if...else if...逻辑
    • 由于异常类有一定的继承层次,需要把子类类型的异常放在最前面(如果不是这样,一般会有编译错误)
  • 应用实例

    • 应用实例1,进行异常处理后的程序,程序不会崩溃

      package com.bjpowernode.demo;

      /**
      * 异常示例
      */
      public class ExceptionDemo {
      public static void main(String[] args) {
      //调用方法
      ExceptionDemo.exception();
      }

      /**
      * 异常处理,方式1:自己处理
      */
      public static void exception(){
      try{
      //产生异常
      //空指针异常
      String str=null;
      System.out.println(str.length());
      //数组越界异常
      int[] datas = new int[0];
      datas[0]=2;
      //被0除异常
      int result = 5/0;

      //可能产生异常代码块中,异常产生后的代码块,将不会被执行
      System.out.println("产生异常后,try中的其他代码块。");
      }catch (NullPointerException ex){
      //主动输出异常栈,里面的内容有指向性意义,比如行号
      ex.printStackTrace();
      //空指针异常
      System.out.println("空指针异常,异常信息:"+ex.getMessage()); //自定义输出
      }catch (ArrayIndexOutOfBoundsException ex){
      //数组越界异常
      System.out.println("数组越界异常,异常信息:"+ex.getMessage()); //自定义输出
      }catch (Exception ex){
      //其他异常,因为Exception是所有异常类的父类,将进行兜底
      System.out.println("其他异常,异常信息:"+ex.getMessage()); //自定义输出
      }finally {
      System.out.println("finally,每次都会输出。");
      }

      //异常后的逻辑,由于前面异常将导致程序崩溃,此处代码块会被执行
      System.out.println("异常处理后的代码块。");
      }
      }

【练习】

  1. 练习演示的内容,完成代码编写

调用者处理

  • 同样,在一个方法中,也可以不处理可能会产生的异常,交由方法的调用者进行处理

  • 并且,在需要的时候,可以主动使用throw抛出一个异常类对象,交由方法的调用者进行处理

  • 那么,这个方法的声明后(方法体前),需要使用throws声明此方法需要调用者处理的异常

  • 语法规则:

    • throw抛出异常

      throw 异常类对象;
    • throws声明方法抛出的异常,如果是编译期异常在方法中使用throw抛出异常,且并未使用try...catch进行异常捕获,就必须在方法声明后添加该异常的声明

      访问修饰符 [其他修饰符] 返回值类型 方法名([参数类型 参数名,...]) throws 异常类型{
      //方法体
      return 返回值;
      }
  • 应用实例

    • 应用实例1,抛出异常,并在方法上声明编译期异常

      package com.bjpowernode.demo;

      import java.io.IOException;

      /**
      * 异常示例
      */
      public class ExceptionDemo {
      public static void main(String[] args) {
      //调用方法,需要处理调用方法中抛出的异常
      try {
      ExceptionDemo.exception();
      } catch (IOException ex) {
      //捕获并处理异常
      System.out.println("IO异常,异常信息:" + ex.getMessage());
      }
      }

      /**
      * 异常处理,方式2:调用者处理,方法签名中需要加上throws声明
      */
      public static void exception() throws IOException {
      //在方法中主动抛出一个IO异常
      throw new IOException("我是IO异常");

      }
      }

【练习】

  1. 练习演示的内容,完成代码编写

异常分类

在Java的异常处理体系中,将Exception分为了两类

运行时异常(非检查性异常),继承于RuntimeException

  • 编译时发现不了的问题,但是在代码运行的过程中可能会出现的问题。当出现运行时异常的时候,程序直接就中断了,但是也可以通过异常处理机制,捕捉到异常,恢复程序的继续执行。
  • 不需要使用try...catch强制捕获处理
  • 常见的有:
    • NullPointerException:空指针异常
    • IndexOutOfBoundsException:索引越界异常
    • ClassCastException:类转换异常
    • ArithmeticException:算术异常
    • ...

编译时异常(检查性异常),继承于Exception

  • 编译器在编译你的代码的时候发现你的代码可能会出现的问题,就会提示你要去手动处理这个异常,否则代码编译不通过,所以叫编译时异常。

  • 需要使用try...catch强制捕获处理,或者用throws声明由方法调用者捕获处理

  • 常见的有:

    • InterruptedException:一个线程被另一个线程中断,抛出该异常。

    • IOException:IO异常

    • SQLException:SQL异常

    • ClassNotFoundException:找不到类异常

    • ...

异常结构体系

  • 在Java中,异常分为Error和Exception两个系列,类型名称分别为xxxError和xxxException

  • Error系列

    • 最严重错误
    • 程序无法处理,无法通过try...catch进行捕获处理,但可能由代码不合理导致
    • 由JVM负责处理,如内存不足、堆栈溢出、程序奔溃等
  • Exception系列

    • 分运行时异常和编译时异常
      • 运行时异常,继承于RuntimeException,可使用try...catch进行捕获处理
      • 编译时异常,直接继承于Exception,必须使用try...catch捕获,或者在方法签名后使用throws关键字声明需要调用方进行处理
    • 需要在代码中进行捕获,并处理,来保证程序的健壮性
  • 其结构如下图

    异常处理-异常结构体系

自定义异常类【扩展】

  • 在一些工具开发场景,或一些项目的开发中,会针对工具或项目中的意外情况给使用方提供合理的提示信息,多用自定义异常实现

  • 自定义异常一般继承Exception类,并可添加一些自己的属性和方法

  • 应用实例

    • 应用实例1,定义一个自定义异常,并进行抛出、捕获、处理

      package com.bjpowernode.demo;

      /**
      * 自定义异常
      */
      public class MyException extends Exception {

      public MyException() {
      super("自定义异常");
      }

      public MyException(String message) {
      super(message);
      }

      public static void main(String[] args) {
      //捕获并处理自定义异常
      try {
      MyException.exception();
      } catch (MyException ex) {
      //捕获并处理异常
      System.out.println("自定义异常,异常信息:" + ex.getMessage());
      }finally {
      System.out.println("finally,每次都会输出。");
      }

      System.out.println("异常处理后的代码块。");
      }

      /**
      * 抛出自定义异常
      */
      public static void exception() throws MyException {
      //在方法中主动抛出一个自定义异常
      throw new MyException();
      }
      }

【练习】

  1. 练习演示的内容,完成代码编写

实战和作业

  1. 重构程序,针对”第11章-多态“中的people项目中各个国家的人的姓名和年龄进行处理,具体要求如下:
    1. 添加年龄属性,并生成getter/setter
    2. 自定义异常PeopleException,作为整个项目的业务异常类
    3. 在各个国家人姓名设置时,进行验证,是否包含数字(可将字符串转成char[]数组,然后使用包装类Character的isDigit静态方法验证每个字符是否为数字)
      1. 如果包含,抛出PeopleException异常,异常信息为"姓名不能包含数字"
      2. 如果不包含,正常设置值
    4. 在各个国家人年龄设置时,进行验证,年龄不能小于0
      1. 如果小于0,抛出PeopleException异常,异常信息为"年龄不能小于0"
      2. 如果大于等于0,正常设置值
    5. 定义测试类,测试正常设置和抛出异常的情况