Java 的异常处理机制

错误与异常

在 Java 中所有的错误 (Error) 和异常 (Exception) 都继承了同一个父类 Throwable,它们的关系如下:

  • Error (错误):是指程序无法处理的错误,表示应用程序运行时遇到比较严重的问题。大多数错误与开发者执行的操作无关,而表示在代码运行时 JVM 出现了的问题。
  • Exception (异常):是指在程序运行时由于程序处理逻辑上的错误而导致程序中断的一种指令流。通俗地说,就是代码存在逻辑错误(Bug)。
  • 上述两者的区别:错误无法被处理,异常可以被程序自身捕获并处理

异常分为两大类

  • 编译时异常(Exception):即编译器检查出的异常,是编译器要求必须处理的异常。
  • 运行时异常(RuntimeException):即程序运行时发生的异常,编译器不强制要求处理的异常,程序员应该避免出现的异常。java.lang.RuntimeException 类及它的子类都是运行时异常。对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。

常见的编译时异常

编译时异常说明
SQLException 操作数据库时,查询表可能发生异常
IOException 操作文件时发生的异常
FileNotFoundException 当操作一个不存在的文件时,发生异常
ClassNotFoundException 加载类,但是该类不存在时,发生异常
EOFException 操作文件,到文件末尾,发生异常

常见的运行时异常

运行时异常说明
NullPointerException 空指针异常
ArithmeticException 数学运算异常
ClassCastException 类型转换异常
NumberFormatException 数字格式不正确异常
IllegalArgumentException 非法参数异常
ArrayIndexOutOfBoundsException 数组下标越界异常

异常的两种处理方式

  • 第一种方式:try-catch-finally

    • 在 try 语句中捕获可能出现异常的代码,如果出现异常,则跳转到 catch 语句中找到对应的异常类型进行处理。
    • 最后不管有没有异常,都会执行 finally 中的代码块。
    • finally 代码块是可以省略的,如果省略,则在执行完 catch 代码块之后,程序继续执行后边的代码。
  • 第二种方式:throws

    • 将发生的异常抛出,交给调用者(方法)来处理,最顶级的异常处理者就是 JVM。
    • 如果一个方法在执行时可能产生某种异常,但是并不确定如何处理这种异常,则该方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
    • 在方法声明中用 throws 关键字可以声明抛出异常的列表,throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
    • 对于编译时异常,程序必须处理,比如使用 try-catch 或者 throws 的方式。
    • 对于运行时异常,如果程序没有处理,默认就是以 throws 的方式处理。
    • 子类重写父类的方法时,所抛出的异常类型要么和父类抛出的异常一致,要么是父类抛出的异常类型的子类型。一句话简单概况:子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
    • 在 throws 过程中,如果有方法 try-catch,就相当于处理异常,就可以不必 throws。

特别注意

使用 catch 处理多个异常时,捕获范围小的异常要放在捕获异常范围大的异常之前处理,否则代码无法通过编译器的编译。

异常的处理流程

  • (1) Java 中可以处理的异常都是在运行时产生的,当程序运行到产生异常的代码时,会由 JVM 帮助用户去判断此异常的类型,并自动进行指定类型的异常类对象实例化处理。
  • (2) 如果此时程序中并没有提供异常处理,则会采用 JVM 默认的异常处理方式进行处理,首先进行异常信息的打印,然后直接退出当前程序。
  • (3) 如果此时程序中存在异常处理,那么这个异常类的实例化对象会被 try 语句所捕获。
  • (4) try 捕获到异常后,将会与和它匹配的 catch 中的异常类型依次进行比对,如果相同则进行处理;如果不匹配,则继续匹配后续的 catch 类型;如果都不匹配,那么表示该异常无法处理。
  • (5) 不管异常是否被处理,最终都会执行 finally 代码块,执行完 finally 代码块后,程序会进一步判断当前的异常是否已经处理,如果处理了,则继续执行后续的代码;如果没有,则交给 JVM 进行默认处理。

throws 和 throw 的区别

在默认情况下,所有的异常类的实例化对象都是由 JVM 默认实例化并且自动抛出。为了方便用户手动进行异常的抛出,JVM 提供了 throw 关键字。throws 和 throw 的区别如下:

意义位置后面跟的
throws 异常处理的一种方式方法声明处异常类型
throw 用于手动抛出异常对象方法体中异常对象

自定义异常类

  • 当程序出现某些 “错误”,但该错误信息并没有在 Throwable 子类中描述处理,此时可以自定义异常类,用于描述该错误信息。
  • 自定义异常类可以继承自 Exception(编译时异常)或 RuntimeException(运行时异常)。
  • 一般情况下,自定义异常是继承自 RuntimeException 类,把自定义异常做成运行时异常,这样做的好处是可以使用默认的异常处理机制,比较方便。