Java面试题 — Java中的异常处理机制是怎样的?

0 阅读7分钟

Author : Cyan_RA9
Source : 【卡码笔记】网站
Question : Java中的异常处理机制是怎样的?

【简要回答】

异常的概念

  1. 定义
    • 异常,即程序编译过程中 或 运行过程中出现的非正常情况,通俗地说,异常就是程序出现的非语法错误,最终会导致JVM的非正常停止。
  2. 分类
    • Java中Throwable类是所有异常和错误的超类(父类),即异常的顶层父类。Throwable类属于java.base模块,java.lang包下,它下面有两个子类Error 和 Exception,前者表示"错误",后者表示"异常"。如下图所示:
      Exception_category.jpg

异常的处理

  1. JVM的默认处理方式
    • 创建对应异常类的对象并抛出一个异常对象,然后JVM会以红色的字体在控制台打印出错误信息,并终止程序。
  2. 常见的手动处理方式
    • throws关键字 —— 通过throws关键字将异常抛给调用者,最终抛给JVM,JVM做中断处理。
    • try-catch代码块 —— 通过try...catch...方法自己来捕获并处理异常
  3. Throwable类中的三个常用异常处理方法
    • Throwable类中有三个方法可用于 try-catch 语句中的catch语句里面,通过捕获到的异常对象来调用方法,以作为异常处理逻辑——
      String getMessage() --- 返回此异常的简短描述。
      String toString() --- 返回此异常的详细消息字符串,直接输出异常对象默认会调用此方法
      void printStackTrace() --- JVM打印异常对象就是默认此方法,打印的异常信息是最全面的。
  4. finally代码块
    • finally代码块是对 try-catch 语句的补充和完善,finally中的代码一定会执行

【详细回答】

异常的概念

  1. 定义
    • 异常,即程序编译过程中 或 运行过程中出现的非正常情况,通俗地说,异常就是程序出现的非语法错误,最终会导致JVM的非正常停止。
    • 异常并不是语法错误,语法错了,编译不通过,就不会产生字节码文件,也就不能运行。
  2. 分类
    • Java中 Throwable类 是所有异常和错误的超类(父类),即异常的顶层父类。Throwable类属于java.base模块,java.lang包下,它下面有两个子类 Error 和 Exception,前者表示"错误",后者表示"异常"。
    • 异常(Exception)
      • 指合理的应用程序可能需要捕获的问题。eg:NullPointerException(空指针异常),ArithmeticException(运算条件异常)。
      • 异常又可以分为 编译期异常运行期异常 —— 编译期异常,即进行编译时出现的问题,必须对其采取相应的处理机制;运行期异常,即代码运行中出现的问题,默认交给JVM来处理,RuntimeException 及其所有子类异常 都属于运行期异常。
    • 错误(Error)
      • 指合理的应用程序不应该试图捕获的,严重的问题,是不可以通过异常处理机制来解决的。eg:StackOverFlowError(栈内存溢出),OutOfMemoryError(内存溢出)。

异常的处理

  1. JVM的默认处理方式
    • 创建对应异常类的对象并抛出一个异常对象,然后JVM会以红色的字体在控制台打印出错误信息,并终止程序。
  2. 常见的手动处理方式
    • throws关键字 —— 通过throws关键字将异常抛给调用者,最终抛给JVM,JVM做中断处理。。如果抛出的多个异常具有子父类关系,直接声明父类异常即可(即直接抛出父类异常),特别适用于出现多个编译期异常且这些异常有统一具体的父类时,比如在java 反射或者java IO流中常见。
    • try-catch代码块 —— 通过try...catch...方法自己来捕获并处理异常
  3. Throwable类中的三个常用异常处理方法
    • Throwable类中有三个方法可用于 try-catch 语句中的catch语句里面,我们可以利用捕获到的异常对象来调用方法,来作为异常处理逻辑——
      String getMessage() --- 返回此异常的简短描述 (原因)。
      String toString() --- 返回此异常的详细消息字符串 (内容、原因),直接打印异常对象默认会调用此方法
      void printStackTrace() --- JVM打印异常对象就是默认此方法,打印的异常信息是最全面的 (内容、原因、位置)。
  4. finally代码块
    • finally代码块是对 try-catch 语句的补充和完善,finally中的代码一定会执行。使用finally代码块要注意以下几点——
      1. finally 不能单独使用,必须与try..catch...语句一起。
      2. finally 一般用于资源释放资源回收,适用于“无论程序是否出现异常,最后都要释放资源”的场景(常见于IO流)。
      3. 如果finally中含有return语句,会永远只返回finally中return的结果,因此要避免此情况发生。
        • 若 finally代码块 之前的 try-catch语句 中也有 return语句,那么该 return语句 也会执行,但是程序并不会由该return语句返回,而是在底层用一个 临时变量temp 保存 该return的内容,最终的返回值仍是以 finally代码块 中的return语句为准。若 finally代码块 中无return语句,才会返回 临时变量temp 保存的值。
        • 有时会出现 try-finally 的搭配,这种用法相当于没有捕获异常,因此若出现异常程序会直接崩掉。这种搭配适用于“执行一段代码,不管有没有出现异常都要执行某个业务逻辑”的场景 (例如,关闭文件、关掉数据库连接、释放锁等)。

异常的产生机制

  1. 程序执行到代码异常处时,JVM检测出程序出现异常,这时它会 根据异常产生的原因 创建一个异常对象,这个异常对象包含了异常产生的(内容,原因,位置)
  2. 产生异常的方法 中没有相应的异常处理机制,那么JVM就会把 产生的异常对象 抛给方法的调用者(例如main函数)来处理这个异常。
  3. 方法的调用者(例如main方法)接收到了这个异常对象后,若调用者也没有异常处理机制, 就会继续把 异常对象 抛给调用者的调用者(main方法的调用者就是JVM了)来处理。
  4. 产生的异常对象 最终抛给了JVM,当JVM收到 main方法 抛来的 异常对象 后,它会在控制台打印出这个异常对象,并终止程序
  • 异常的产生机制,图解如下:

    exception_mechanism.jpg


【知识拓展】

  1. 关于 throw 关键字
    • 作用:throw 关键字可以在在指定方法中抛出异常对象,而且可以自己更改提示信息。
    • 格式throw new xxxException("异常产生的原因");
    • 注意事项:throw关键字必须写在方法的内部,而且 new 出来的对象必须是 Exception类 或者 Exception类子类 的对象。
  2. 多异常的处理方式
    • 分别捕获,并分别处理
    • 一次捕获,但多次处理
    • 一次捕获,且一次处理
  3. 子父类异常的关系
    • 如果父类中抛出异常,子类重写父类方法时, 抛出和父类相同的异常,或者是父类异常的子类,或者不抛出
    • 如果父类中没有抛出异常,子类重写父类该方法时, 也不可抛出异常。此时子类若产生了异常,只能做捕获处理,不能声明抛出。
  4. 自定义异常
    • 格式

      public class xxxException extends Exception / RuntimeException {
      	//自定义的异常类内部,需要定义两个构造方法——
      	// 一个空参构造
      	// 一个带参构造(说明异常信息)
      }
      
    • 注意事项
      ① 自定义的异常类,必须得继承Exception类或者RuntimeException类:继承Exception类,表示自定义的异常类是一个编译期异常;继承RuntimeException类,表示自定义的异常类是一个运行期异常;