zoukankan      html  css  js  c++  java
  • java-异常体系

    1 异常的继承体系结构

    • Throwable 类是 Java 语言中所有错误或异常的超类。
    • 只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
    • Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。
    • 最后,它还可以包含 cause(原因):另一个导致此 throwable 抛出的 throwable。此 cause 设施在 1.4 版本中首次出现。它也称为异常链 设施,因为 cause 自身也会有 cause,依此类推,就形成了异常链,每个异常都是由另一个异常引起的。

    1.1 Error

    • Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题
    • 大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。
    • 在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。
    • Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外

    1.2 Exception

    • Exception 异常主要分为两类

      • 一类是 IOException(I/O 输入输出异常),其中 IOException 及其子类异常又被称作「受查异常」
      • 另一类是 RuntimeException(运行时异常),RuntimeException 被称作「非受查异常」。
    • 受查异常就是指,编译器在编译期间要求必须得到处理的那些异常,你必须在编译期处理了

    1.2.1 常见的非检查性异常:

    下载.png

    1.2.2 常见的检查性异常:

     

    2 自定义异常类型

    Java 的异常机制中所定义的所有异常不可能预见所有可能出现的错误,某些特定的情境下,则需要我们自定义异常类型来向上报告某些错误信息。

    • 在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
      • 所有异常都必须是 Throwable 的子类。
      • 如果希望写一个检查性异常类,则需要继承 Exception 类。
      • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

    3 异常的处理方式

    3.1 try...catch关键字

    • 使用 try 和 catch 关键字可以捕获异常。
    • try/catch 代码块放在异常可能发生的地方。

    try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

    try {
       // 程序代码
    } catch(ExceptionName e1) {
       //Catch 块
    }
    • Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。
    • 一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获
    • 多重捕获块的语法如下所示:
    try{
       // 程序代码
    }catch(异常类型1 异常的变量名1){
      // 程序代码
    }catch(异常类型2 异常的变量名2){
      // 程序代码
    }catch(异常类型2 异常的变量名2){
      // 程序代码
    }

    3.2 throws/throw 关键字

    • 如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
    • 下面方法的声明抛出一个 RemoteException 异常
    public class className {
      public void deposit(double amount) throws RemoteException {
        // Method implementation
        throw new RemoteException();
      }
      //Remainder of class definition
    }

    一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

    3.3 finally关键字

    • finally 关键字用来创建在 try 代码块后面执行的代码块。
    • 无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
    • finally 代码块出现在 catch 代码块最后,语法如下:
    try{
      // 程序代码
    }catch(异常类型1 异常的变量名1){
      // 程序代码
    }catch(异常类型2 异常的变量名2){
      // 程序代码
    }finally{
      // 程序代码
    }

    4 try-catch-finally 的执行顺序

    try-catch-finally 执行顺序的相关问题可以说是各种面试中的「常客」了,尤其是 finally 块中带有 return 语句的情况。我们直接看几道面试题:

    4.1 面试题一:

    public static void main(String[] args){
        int result = test1();
        System.out.println(result);
    }
    
    public static int test1(){
        int i = 1;
        try{
            i++;
            System.out.println("try block, i = "+i);
        }catch(Exception e){
            i--;
            System.out.println("catch block i = "+i);
        }finally{
            i = 10;
            System.out.println("finally block i = "+i);
        }
        return i;
    }

    输出结果如下

    try block, i = 2
    finally block i = 10
    10

    这算一个相当简单的问题了,没有坑,下面我们稍微改动一下

    public static int test2(){
        int i = 1;
        try{
            i++;
            throw new Exception();
        }catch(Exception e){
            i--;
            System.out.println("catch block i = "+i);
        }finally{
            i = 10;
            System.out.println("finally block i = "+i);
        }
        return i;
    }
    catch block i = 1
    finally block i = 10
    10

    4.2 面试题二

    public static void main(String[] args){
        int result = test3();
        System.out.println(result);
    }
    
    public static int test3(){
        //try 语句块中有 return 语句时的整体执行顺序
        int i = 1;
        try{
            i++;
            System.out.println("try block, i = "+i);
            return i;
        }catch(Exception e){
            i ++;
            System.out.println("catch block i = "+i);
            return i;
        }finally{
            i = 10;
            System.out.println("finally block i = "+i);
        }
    }

    输出结果如下:

    try block, i = 2
    finally block i = 10
    2

    是不是有点疑惑?明明我 try 语句块中有 return 语句,可为什么最终还是执行了 finally 块中的代码?

    我们反编译这个类,看看这个 test3 方法编译后的字节码的实现:

    0: iconst_1         //将 1 加载进操作数栈
    1: istore_0         //将操作数栈 0 位置的元素存进局部变量表
    2: iinc          0, 1   //将局部变量表 0 位置的元素直接加一(i=2)
    5: getstatic     #3     // 5-27 行执行的 println 方法                
    8: new           #5                  
    11: dup
    12: invokespecial #6                                                     
    15: ldc           #7 
    17: invokevirtual #8                                                     
    20: iload_0         
    21: invokevirtual #9                                                     24: invokevirtual #10                
    27: invokevirtual #11                 
    30: iload_0         //将局部变量表 0 位置的元素加载进操作栈(2)
    31: istore_1        //把操作栈顶的元素存入局部变量表位置 1 处
    32: bipush        10 //加载一个常量到操作栈(10)
    34: istore_0        //将 10 存入局部变量表 0 处
    35: getstatic     #3  //35-57 行执行 finally中的println方法             
    38: new           #5                  
    41: dup
    42: invokespecial #6                  
    45: ldc           #12                 
    47: invokevirtual #8                  
    50: iload_0
    51: invokevirtual #9                
    54: invokevirtual #10                 
    57: invokevirtual #11                 
    60: iload_1         //将局部变量表 1 位置的元素加载进操作栈(2)
    61: ireturn         //将操作栈顶元素返回(2)
    -------------------try + finally 结束 ------------
    ------------------下面是 catch + finally,类似的 ------------
    62: astore_1
    63: iinc          0, 1
    .......
    .......
    • 从我们的分析中可以看出来,finally 代码块中的内容始终会被执行,无论程序是否出现异常的原因就是,编译器会将 finally 块中的代码复制两份并分别添加在 try 和 catch 的后面

    可能有人会所疑惑,原本我们的 i 就被存储在局部变量表 0 位置,而最后 finally 中的代码也的确将 slot 0 位置填充了数值 10,可为什么最后程序依然返回的数值 2 呢?

    • 仔细看字节码,你会发现在 return 语句返回之前,虚拟机会将待返回的值压入操作数栈,等待返回,即使 finally 语句块对 i 进行了修改,但是待返回的值已经确实的存在于操作数栈中了,所以不会影响程序返回结果。

    4.3 面试题三

    public static int test4(){
        //finally 语句块中有 return 语句
        int i = 1;
        try{
            i++;
            System.out.println("try block, i = "+i);
            return i;
        }catch(Exception e){
            i++;
            System.out.println("catch block i = "+i);
            return i;
        }finally{
            i++;
            System.out.println("finally block i = "+i);
            return i;
        }
    }

    运行结果:

    try block, i = 2
    finally block i = 3
    3

    其实你从它的字节码指令去看整个过程,而不要单单记它的执行过程。

    你会发现程序最终会采用 finally 代码块中的 return 语句进行返回,而直接忽略 try 语句块中的 return 指令

    最后,对于异常的使用有一个不成文的约定:尽量在某个集中的位置进行统一处理,不要到处的使用 try-catch,否则会使得代码结构混乱不堪。



  • 相关阅读:
    yii2 页面渲染方法解析
    JavaScript 编码小技巧
    Ansible Playbooks入门介绍
    CentOS 7 源码安装Ansible 2.x
    GitLab 安装与入门
    SpringBoot 悲观锁 与 乐观锁
    SpringBoot 事务隔离性和传播性
    SpringBoot 定义通过字段验证
    SpringBoot 密码MD5加密
    SpringBoot MockMVC
  • 原文地址:https://www.cnblogs.com/zouhong/p/13394327.html
Copyright © 2011-2022 走看看