JavaSE:异常


异常:程序在运行过程中出现的特殊情况。

异常是一个事件,干扰程序的正常执行。

1、异常体系

1.1、API 层次

java.lang.Throwable:所有异常和错误的父类。

包含线程执行堆栈的快照,提供 printStackTrace() 等 API 用于获取堆栈信息。

  • Error:JVM、硬件资源等错误,无法手动处理

    • 示例:StackOverFlowError、OOM、NoClassDefFoundError
  • Exception:程序编译和运行时产生的问题,可处理

    • 运行时异常:RuntimeException 及其子类。如空指针、下标越界、类型转换异常...

    • 编译时异常:RuntimeException 以外的异常。如 IO、SQL 异常。

      image-20220419122429335

1.2、异常类型

Java 异常分为两类:非受检异常(不可查)

  • 非受检异常(unchecked)

    • 包含运行时异常(Exception 及其子类)、错误(Error)。
    • 不要求强制处理的异常,即可处理或不处理
  • 受检异常(checked)

    • 包含 Exception 中的非运行时异常
    • Java 编译器会检查此类异常,要求必须处理的异常(捕获 or 抛出)。

2、异常使用

2.1、关键字

作用 含义
try 监听 将可能抛出异常的代码放在 try 块中
catch 捕获 捕获 try 块中的异常
finally 最终执行 即使发生异常也会执行,通常用于释放资源
throw 抛出 手动抛出异常
throws 声明方法异常 在方法签名中声明此方法可能抛出的异常

2.2、异常产生

  • 系统产生:程序运行时遇到不合规范的代码或结果时,自动产生异常。
  • 手动抛出:使用 throw 关键字手动抛出异常。
    • 通常不需要手动抛出异常,而是将系统产生的异常捕获向上抛出
    • 可以在 catch 中抛出异常,目的是改变异常类型。

2.3、异常处理

说明

  1. 受检异常必须处理,非受检异常可不处理。
  2. 异常的传递:按方法调用链反向传递。
    • 若始终向上抛出,最终由 JVM 进行默认异常处理
    • 即打印堆栈跟踪信息(e.printStackTrace()),并终止程序。

处理方式

  1. 捕获:使用 try-catch 块捕获方法异常。
  2. 向上抛出:在方法签名中使用 throws 关键字,将异常抛给调用者。

异常捕获

  • try-catch
  • try-catch-finally
  • try-finally
  • try-with-resource

① t-c

single-catch

单个 catch 块仅捕获单个异常类型

  • 单个 catch:捕获 try 块中唯一可能发生的异常。

  • 多个 catch:捕获 try 块中多个可能发生的异常。

    • 按 catch 块的声明顺序先后匹配异常。

    • 顺序要求:优先定义最具体的异常类型(先子后父,先小后大)。

      try {
          // ...
      } catch (FileNotFoundException e) {
          // ...
      } catch (IOException e) {
          // ...
      }
      

multi-catch(?)

单个 catch 捕获多个异常类型,用 | 隔开(JDK7 引入)

  • JVM 会检测 try 块中的异常()

  • catch 块中的异常类型没有顺序要求,但不能同时存在子类和父类异常类型,否则编译不通过。

    try {
        // ...
    } catch (FileNotFoundException | NullPointerException e) {
        // ...
    }
    

② t-c-f

  • 没有异常
    1. 执行完 try 块。
    2. 跳过 catch。
    3. 执行 finally。
    4. 跳出 t-c-f。
  • 捕获到异常
    • catch 块中没有定义相应类型的异常
      1. 执行到 try 块的第 k 行,发生异常。
      2. JVM 进行默认异常处理:打印堆栈信息、终止程序。
    • catch 捕获到相应类型的异常
      1. 执行到 try 块的第 k 行,发生异常。
      2. try 中第 k 行之后的代码不会执行,跳到相应的 catch 块
      3. 执行完 catch 块,执行 finally 块。
      4. 跳出 t-c-f。

③ t-f

场景:不需要捕获异常,仅用于释放连接

示例

  1. 使用 时释放资源。
  2. 并发编程中 的使用和释放。
  3. 的释放。

④ t-w-r(?)

JDK7 引入:

  • 作用

    • 自动释放资源,可不写 finally 块。

    • 避免覆盖 try 块中的异常类型,保留所有可能出现的异常。

  • 使用前提:资源实现了 AutoCloseable 接口。

    • 常见接口:InputStream、OutputStream、Connection、Statement、ResultSet 等。

示例

  • 代码:在 try 块中创建资源对象。

    try(InputStream is = new FileInputStream("d:\\1.txt")) {
        System.out.println(is);
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • 等价于:在 try 块中创建资源对象,并再次使用 try-catch 语句释放资源。

    try {
        InputStream is = new FileInputStream("d:\\1.txt");
        // 可能出现的异常
        Throwable t = null;
        try {
            System.out.println(is);
        } catch (Throwable e1) {
            t = e1;
            throw e1;
        } finally {
            // 资源非空,需要释放
            if (is != null) {
                // 内部try块发生异常
                if (t != null) {
                    try {
                        is.close();
                    } catch (Throwable e2) {
                        // 资源释放出现异常,作为被压制异常添加
                        t.addSuppressed(e2);
                    }
                } else {
                    is.close();
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    

2.4、自定义异常

  • 继承 Expectation 超类。

  • 提供 2 个构造方法

    • 无参

    • msg:用于打印异常信息,调试。

      public class MyException extends Exception {
          public MyException(){}
          public MyException(String msg){
              super(msg);
          }
          // ...
      }
      

3、说明

3.1、finally 特殊情况(?)

3.2.1、不被执行

以下情况,finally 不会正常执行。

  1. 执行 finally 块时发生异常,异常之后的代码不会被执行。
  2. 程序所在线程死亡
  3. 执行 finally 块之前 JVM 终止System.exit()
  4. CPU 关闭

3.2.2、return

若在 finally 块中使用 return

  1. 异常吞噬:try 块抛出异常时会跳到 finally 块,而执行 finally 块的 return 时直接返回,没有对异常进行处理。

    public boolean foo() {
        try {
            // 抛出异常
        } finally {
            // ...
            return true;
        }
    }
    
  2. 对方法返回值的影响:try 块会固定作为返回值的变量,finally 块中对该变量进行修改时不会影响返回值

    // 以下程序返回 10
    public int foo() {
        int result;
    
        try {
            result = 10;
            return result;
        } finally {
            result = 20;
            return result;
        }
    }
    

结论:不要在 finally 块中使用 return 关键字

3.2、异常总结

结合 深入理解。

  1. 异常体系
    • API:Error、Exception(运行时、非运行时)
    • 受检异常、非受检异常
  2. 关键字:try、catch、finally、throw、throws
    • try、catch、finally 不能单独使用。
    • 捕获:t-c、t-c-f、t-f、t-w-r
  3. 异常产生:系统产生、throw 产生。
  4. 异常处理:向上抛出、捕获。
  5. 自定义异常