10.1 异常介绍

  • Throwable类: 是 Java 语言中所有错误和异常的超类。有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含了大量的子类。
  • Error类: 是程序无法处理的错误,表示运行应用程序时遇到的较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。遇见这类错误,程序只有终止运行,例如 OutOfMemoryError(继承自 VirtualMachineError)、NoClassDefFoundError(继承自 LinkageError)等。
  • Exception类: 程序运行过程中出现的意外的情况,背离我们程序本身的意图,需要程序员介入处理。

异常和错误的本质区别是:异常能被程序本身处理,而错误无法处理。

Error 是一种严重的问题,应用程序不应该捕捉它。

Error 继承自 Throwable 而不是继承自 Exception,就是为了方便程序可以使用 “catch (Exception)”来捕捉异常而不会把 Error 也捕捉在内。因为 Exception 发生后可以进行一些恢复工作的,但是 Error 发生后一般是不可恢复的。

本章在介绍 Java 异常及异常处理的基础上,介绍 Spring Boot 中如何处理全局异常,及针对某些异常的重试机制。

异常分为编译时异常和运行时异常:

  • 编译时的异常,我们可以直观的看见,可以进行立马的更改,由 Java 编译器把关,不处理(修改代码)编译时异常就不会生成 Java 程序(Java 字节码 class 文件)。
  • 运行时的错误,则是编译器能力之外的事情,我们预料不到的错误,如果不更改,程序就会崩溃,例如用户输一个超越数组大小的数字来访问数组中的元素时,就会出现 ArrayIndexOutOfBoundsException 这个异常。

Java 中的异常体系,简要的如下图所示:

Java 定义了两种类型的异常

  • Checked exception: 继承自 Exception 类的就是 Checked exception。代码需要明确的处理抛出的 Checked exception,要么用 catch 语句,要么直接用 throws 语句抛出去让调用方处理。
  • Unchecked exception: 也称 RuntimeException,它也是继承自 Exception。但所有 Runtime Exception 的子类都有个特点,就是代码不需要处理它们的异常也能通过编译,所以它们称作 Unchecked Exception。RuntimeException(运行时异常)就是不需要 try...catch... 或 throws 机制去处理的异常。

如果出现了 RuntimeException,就一定是程序员自身的问题。比如说,数组下标越界(程序应该控制不越界的下标)和访问空指针异常等等,只要你稍加留心这些异常都是在编码阶段可以避免的异常。

Java 中常见的异常及说明如下:

异常类型 说明
Exception 异常的根类,继承自 Throwable 类
RuntimeException 运行时异常,多数 java.lang 异常的根类
ArithmeticException 算术错误异常,如以零做除数
ArraylndexOutOfBoundException 数组下标小于或大于实际的数组大小
NullPointerException 著名的 NPE,尝试访问 null 对象成员,空指针异常
ClassNotFoundException 不能加载所需的类
NumberFormatException 数字转化格式异常,比如字符串到 float 型数字的转换无效
IOException I/O 异常的根类
FileNotFoundException 找不到文件
EOFException 文件结束
InterruptedException 线程中断
IllegalArgumentException 方法接收到非法参数
ClassCastException 类型转换异常
SQLException SQL 操作数据库异常

在实际工作中最经常遇到的5大异常

异常 示例说明
ArithmeticException int a=0;
int b= 3/a;
ClassCastException Object x = new Integer(0);
System.out.println((String)x);
IndexOutOfBoundsException
ArrayIndexOutOfBoundsException
StringIndexOutOfBoundsException
int [] numbers = { 1, 2, 3 };
int sum = numbers[3];
IllegalArgumentException
NumberFormatException
int a = Interger.parseInt("test");
NullPointerException 著名的 NPE,空指针异常

在 Java 语言中,通过面向对象的方法来处理异常。如果在一个方法的运行过程中发生了异常,则这个方法会产生代表该异常的一个对象,并把它交给运行时的系统,运行时系统寻找相应的代码来处理这一异常。

我们把生成异常对象,并把它提交给运行时系统的过程称为拋出(throw)异常。运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的对象,这一个过程称为捕获(catch)异常。

Java 异常强制用户考虑程序的强健性和安全性。异常处理不应该用来控制程序的正常流程,而应该用来捕获程序在运行时发生的异常行为并进行相应处理,让程序能够回到正常的预期运行状态,按照编写代码时期望的路径上运行(一般而言,就是符合客户需求预期)。

编写代码处理某个方法可能出现的异常,可遵循如下三个原则:

  1. 在当前方法声明中使用 try catch 语句捕获异常。
  2. 一个方法被覆盖时,覆盖它的方法必须拋出相同的异常或异常的子类。
  3. 如果父类抛出多个异常,则覆盖方法必须拋出那些异常的一个子集,而不能拋出新的异常。

典型的异常处理代码:

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

另外,也可以在具体位置不处理异常(无论它是新实例化的还是刚捕获到的),通过 throw 直接抛出到上层再进行处理。这个不处理受查异常(Checked exception)的方法必须使用 throws 关键字来声明(throws 关键字放在方法签名的尾部)。

import java.io.*;
public class TestClass
{
  public void doSomthing(String[] args) throws SomeException
  {
    //程序代码
    throw new SomeException();
  }
  //其他方法
}
下一节:在实际工作中,我们大部分时间在使用 Spring Boot 开发 Web 应用(当然,最经常使用到的是框架是 Spring MVC)。由于和用户多有交互,所以在请求的过程中,发生错误异常是很常见的。Spring Boot 为我们提供了一套默认的异常/错误处理机制,帮助我们来处理交互过程中的异常/错误。