Java 异常详解及相关面试题

174 阅读8分钟

写在前面

  • 文章是在前人的基础上进行总结整理再加上自己的一点理解,仅作为自己学习的记录,不作任何商业用途!
  • 如果在文章中发现错误或者侵权问题,欢迎指出,谢谢!

异常架构

image.png

  • Throwable

    • Throwable 是 Java 语言中所有错误与异常的超类
    • Throwable 包含两个子类:Error 和 Exception
  • Error

    • Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误
    • 此类错误一般表示代码运行时 JVM 出现问题,这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误
  • Exception

    • 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常
    • 运行时异常
      • RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常
      • Java 编译器不会检查它,也就是说当程序中可能出现这类异常时,若既"没有通过 throws 声明抛出它",也"没有用 try-catch 语句捕获它",还是会编译通过
      • 最后会由 JVM 默认的异常处理器来抛出异常
    • 编译时异常
      • Exception 中除 RuntimeException 及其子类之外的异常
      • Java 编译器会检查它,如果程序中出现此类异常,要么通过 throws 进行声明抛出,要么通过 try-catch 进行捕获处理,否则不能通过编译
  • 受检异常和非受检异常

    • Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)
    • 受检异常
      • 编译器要求必须处理的异常,除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常
    • 非受检异常
      • 编译器不会进行检查并且不要求必须处理的异常,该类异常包括运行时异常(RuntimeException 极其子类)和错误(Error)

异常关键字

  • try:用于监听,将要被监听的代码(可能抛出异常的代码)放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出**
  • catch:用于捕获异常,catch 用来捕获 try 语句块中发生的异常
  • finally:finally 语句块总是会被执行。它主要用于回收在 try 块里打开的物力资源(如数据库连接、网络连接和磁盘文件)
    • 只有 finally 块,执行完成之后,才会回来执行 try 或者 catch 块中的 return 或者 throw 语句
    • 如果 finally 中使用了 return 或者 throw 等终止方法的语句,则就不会跳回执行,直接停止
  • throw:用于抛出异常
  • throws:用在方法签名中,用于声明该方法可能抛出的异常

异常处理

image.png

相关面试题

  • Q1:Error 和 Exception 区别是什么?

    • Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复
    • Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行
  • Q2:RuntimeException 异常和受检异常之间的区别?

    • 是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用 RuntimeException 异常
    • 一般异常(checkedException)主要是指 IO 异常、SQL 异常等。对于这种异常,JVM 要求我们必须对其进行 catch 处理,所以面对这种异常,需要写一大堆的 catch 块去处理可能出现的异常
    • 运行时异常(runtimeException)一般不处理,当出现这类异常的时候程序会由 JVM 接管
      • 出现运行时异常的时候,程序会将异常一直向上抛,一直抛到遇到处理代码,如果没有 catch 块进行处理,到了最上层,如果是多线程就有 Thread.run() 抛出,如果不是多线程那么就由 main.run() 抛出。抛出之后,如果是线程,那么该线程也就终止了,如果是主程序,那么该程序也就终止了
      • 其实,运行时异常的也是继承自 Exception,也可以用 catch 块对其处理,只是我们一般不处理罢了,也就是说,如果不对运行时异常进行catch处理,那么结果不是线程退出就是主程序终止
      • 如果不想终止,那么我们就必须捕获所有可能出现的运行时异常
  • Q3:throw 和 throws 的区别是什么?

    • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出
    • throws 关键字用在方法声明上,用来声明多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常
  • Q4:try-catch-finally 中哪个部分可以省略?

    • catch 可以省略
    • try 只适合处理运行时异常,try + catch 适合处理运行时异常 + 普通异常
      • 如果使用 try 处理普通异常却不加以 catch 处理,编译是通不过的,因为编译器硬性规定:普通异常如果选择捕获,则必须用 catch 显示声明以便进一步处理
      • 如果使用 try 处理运行时异常在编译时没有如此规定,所以 catch 可以省略,你加上 catch 编译器也觉得无可厚非
  • Q5:try-catch-finally 中,如果 try catch 中 return 了,finally 还会执行吗?

    • 会执行,在 return 前执行
    • 在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再返回 try 中的返回值,然后如果在 finally 中直接 return 的话,则会抛弃 try 块中的返回值
  • Q6:JVM 是如何处理异常的?

    • 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈
    • JVM 会顺着调用栈去查找看是否有可以处理异常的代码
      • 如果有,JVM 发现可以处理异常的代码时会把发生的异常传递给它,调用它来处理异常
      • 如果没有,JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序
  • Q7:final、finally、finalize 有什么区别?

    • final 可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值
    • finally 一般作用在 try-catch 代码块中,在处理异常的时候,通常我们将一定要执行的代码方法 finally 代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码
    • finalize 属于 Object 类的一个方法,而 Object 类是所有类的父类,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作
  • Q8:Java中异常处理机制的原理?

    • Java 通过面向对象的方式对异常进行处理,Java 把异常按照不同的类型进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它都是 Throwable 或其子类的实例
    • 当一个方法出现异常后就会抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并对异常进行处理
    • Java 的异常处理是通过 5 个关键词来实现的:try catch  throw  throws  finally

参看与感谢