概述

异常:程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。Java 提供了针对异常的解决办法 —— 异常处理机制。

  • 异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰
  • Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过 throw 语句手动抛出的,只要在 Java 程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE 就会试图寻找异常处理程序来处理异常
  • Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK 中内建了一些常用的异常类,我们也可以自定义异常


Java 异常分类

Java 异常的顶级父类:Throwable

Throwable 派生出 Error 类和 Exception 类:

  • Error:错误,代表了 JVM 本身的错误,不能被程序员通过代码处理
  • Exception:异常,代表了程序运行时的不被期望的事件,是 Java 异常处理的核心


Exception 又分为运行时异常(RuntimeException)和非运行时异常:

  • 运行时异常,又叫非检查异常(unchecked exception),是 RuntimeException 及其子类异常,这些异常是不检查异常,程序中可以选择捕获处理该类异常,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。
  • 非运行时异常,又叫检查异常(checked exception),是除 RuntimeException 及其子类异常之外的异常,javac 强制要求程序员为该类异常做预备处理工作,即使用 try … catch … finally … 对该类异常进行处理或使用 throws 子句进行抛出,否则编译不会通过


异常类结构图:

Java 异常 - 图1

Java 中常见的运行时异常:

ArithmeticException 算术错误异常,如以零做除数
ArraylndexOutOfBoundException 数组索引越界
ArrayStoreException 向类型不兼容的数组元素赋值
ClassCastException 类型转换异常
IllegalArgumentException 使用非法实参调用方法
lIIegalStateException 环境或应用程序处于不正确的状态
lIIegalThreadStateException 被请求的操作与当前线程状态不兼容
IndexOutOfBoundsException 某种类型的索引越界
NullPointerException 尝试访问 null 对象成员,空指针异常
NegativeArraySizeException 再负数范围内创建的数组
NumberFormatException 数字转化格式异常,比如字符串到 float 型数字的转换无效
TypeNotPresentException 类型未找到

Java 中常见的非运行时异常:

异常类型 说明
ClassNotFoundException 没有找到类
IllegalAccessException 访问类被拒绝
InstantiationException 试图创建抽象类或接口的对象
InterruptedException 线程被另一个线程中断
NoSuchFieldException 请求的域不存在
NoSuchMethodException 请求的方法不存在
ReflectiveOperationException 与反射有关的异常的超类

Java 异常处理实践原则

  1. 使用异常,而不是返回码(或类似),因为异常会更加的详细
  2. 主动捕获检查性异常,并对异常信息进行反馈(日志或标记)
  3. 保持代码整洁,一个方法中不要有多个 try … catch 或者嵌套的 try … catch
  4. 捕获更加具体的异常,而不是通用的 Exception
  5. 合理设计自定义的异常类


异常的抛出

异常是在执行某个函数时引发的,而函数又是层级调用形成调用栈的。因此,只要一个函数发生了异常,那么它的所有 caller 都会被异常影响。当这些被影响的函数以异常信息输出时,就形成了异常追踪栈
异常最先发生的地方,叫作异常抛出点
Java 异常 - 图2

从上面的例子可以看出,当devide函数发生除0异常时,devide 函数将抛出ArithmeticException 异常,因此调用他的 CMDCalculate 函数也无法正常完成,因此也发送异常,而 CMDCalculate 的caller —— main 因为 CMDCalculate 抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。

异常的基本语法

在编写代码处理异常时,对于检查异常,有2种不同的处理方式:

  1. 使用try…catch…finally语句块处理它
  2. 在函数签名中使用 throws 声明交给函数调用者 caller 去解决

try…catch…finally 语句块:

  1. try{
  2. //try块中放可能发生异常的代码。
  3. //如果执行完try且不发生异常,则接着去执行 finally 块和 finally 后面的代码(如果有的话)。
  4. //如果发生异常,则尝试去匹配 catch 块。
  5. }catch(SQLException SQLexception){
  6. //每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
  7. //catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
  8. //在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
  9. //如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
  10. //如果try中没有发生异常,则所有的catch块将被忽略。
  11. }catch(Exception exception){
  12. //...
  13. }finally{
  14. //finally块通常是可选的。
  15. //无论异常是否发生,异常是否匹配被处理,finally都会执行。
  16. //一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
  17. //finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
  18. }

注意事项:

  1. try、catch、finally 三个代码块中的局部变量不能共享使用
  2. 每一个 catch 块用于处理一个异常。异常匹配是按照 catch 块的顺序从上往下寻找的,只有第一个匹配的 catch 会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
  3. java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理 catch 代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
    • 有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做:resumption model of exception handling(恢复式异常处理模式 )
    • 而 Java 则是让执行流恢复到处理了异常的 catch 块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)
  1. try {
  2. System.out.println("异常点前的语句块");
  3. int a = 4/0;
  4. System.out.println("异常点后的语句块"); // 该语句无法访问
  5. } catch (ArithmeticException e) {
  6. System.out.println("执行 catch 语句块!");
  7. }
  8. System.out.println("catch代码块后的语句");
  1. finally 块不管异常是否发生,只要对应的 try 执行了,则 finally 块一定会执行。
  2. try … catch … finally 中存在 return 语句时,存在如下情况:
    • finally 语句会在 try 语句的 return 语句之前执行
    • finally 语句中的 return 语句会覆盖 try 和 catch 中的 return 语句
    • try 和 catch 中的 return 语句根据是否发生异常能否到达有关


throws 函数声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则 javac 保证你必须在方法的签名上使用 throws关键字声明这些可能抛出的异常,否则编译不通过。
采取 throws 异常处理的原因:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。

throw 异常抛出语句:throw new Throwable(),显示抛出一个异常,与 JRE 自动形成的异常点没有区别。

自定义异常

  • 自定义检查异常,继承 Exception 类
  • 自定义运行时异常,继承 Runtime 类


自定义异常类包含的元素:

  • 一个无参构造函数
  • 一个带有 String 参数的构造函数,并传递给父类的构造函数
  • 一个带有 String 参数和 Throwable 参数,并都传递给父类构造函数
  • 一个带有 Throwable 参数的构造函数,并传递给父类的构造函数

IOException 源码示例:

  1. public class IOException extends Exception {
  2. static final long serialVersionUID = 7818375828146090155L;
  3. /**
  4. * Constructs an {@code IOException} with {@code null}
  5. * as its error detail message.
  6. */
  7. public IOException() {
  8. super();
  9. }
  10. /**
  11. * Constructs an {@code IOException} with the specified detail message.
  12. *
  13. * @param message
  14. * The detail message (which is saved for later retrieval
  15. * by the {@link #getMessage()} method)
  16. */
  17. public IOException(String message) {
  18. super(message);
  19. }
  20. /**
  21. * Constructs an {@code IOException} with the specified detail message
  22. * and cause.
  23. *
  24. * <p> Note that the detail message associated with {@code cause} is
  25. * <i>not</i> automatically incorporated into this exception's detail
  26. * message.
  27. *
  28. * @param message
  29. * The detail message (which is saved for later retrieval
  30. * by the {@link #getMessage()} method)
  31. *
  32. * @param cause
  33. * The cause (which is saved for later retrieval by the
  34. * {@link #getCause()} method). (A null value is permitted,
  35. * and indicates that the cause is nonexistent or unknown.)
  36. *
  37. * @since 1.6
  38. */
  39. public IOException(String message, Throwable cause) {
  40. super(message, cause);
  41. }
  42. /**
  43. * Constructs an {@code IOException} with the specified cause and a
  44. * detail message of {@code (cause==null ? null : cause.toString())}
  45. * (which typically contains the class and detail message of {@code cause}).
  46. * This constructor is useful for IO exceptions that are little more
  47. * than wrappers for other throwables.
  48. *
  49. * @param cause
  50. * The cause (which is saved for later retrieval by the
  51. * {@link #getCause()} method). (A null value is permitted,
  52. * and indicates that the cause is nonexistent or unknown.)
  53. *
  54. * @since 1.6
  55. */
  56. public IOException(Throwable cause) {
  57. super(cause);
  58. }
  59. }