异常是 Java 程序中经常遇到的问题,一 个异常就是一个 BUG,就要花很多时间来定位异常问题。下面是Java异常类的组织结构。Throwable、IOException、ClassNotFoundException、CloneNotSupportedException及其子异常类表示是程序需要显示捕捉或者抛出的。

深恶痛绝的 Java 异常 - 图1

异常类

Throwable

Throwable是Java异常的顶级类,所有的异常都继承于这个类。Error,Exception是异常类的两个大分类。

Error

是程序无法处理的错误,表示运行应用程序中较严重问题。大多数的错误与代码编写者执行的操作无关,而是表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

Exception

是程序本身可以处理的异常。也就是你常见的空指针异常(NullPointerException),数组超出范围异常(IndexOutOfBoundsException)等等。
通常,Java的异常(包括Exception和Error)分为检查异常(checked exceptions)和非检查的异常(unchecked exceptions)。

异常的分别

检查异常

除了RuntimeException与其子类,以及错误(Error),其他的都是检查异常(绝对的大家族)。

检查异常(checked exceptions)

检查异常是编译器要求必须处置的异常。某段代码,编译器要求必须要对这段代码try…catch,或者throws exception,这就是检查异常,也就是说,代码还没运行编译器就会检查你的代码,会不会出现异常,要求对可能出现的异常必须做出相应的处理。

对检查异常的几种处理方式

  1. 继续抛出,消极的方法,一直可以抛到java虚拟机来处理,就是通过throws exception抛出
  2. 用try…catch捕获

非检查异常(unchecked exceptions)

RuntimeException与其子类,以及错误(Error)。

非检查异常是编译器不要求强制处置的异常。虽然可能出现错误,但不会在编译的时候检查。但一般不处理,因为很难判断会出什么问题,而且有些异常也无法运行时处理,比如空指针,需要手动的去查找。而且,捕捉异常并处理的代价远远大于直接抛出。

对未检查的异常的几种处理方式

  1. 捕获
  2. 继续抛出
  3. 不处理

运行时异常

对Exception异常进行划分,它可分为运行时异常和非运行时异常。

运行时异常(RuntimeException)

RuntimeException 的异常子类不需要强制性处理,可以选择性处理。

运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过,运行时异常可处理或者不处理。运行时异常一般常出来定义系统的自定义异常,业务根据自定义异常做出不同的处理。

常见的运行时异常如NullPointException、ArrayIndexOutOfBoundsException等。

非运行时异常

非运行时异常是程序必须进行处理的异常,捕获或者抛出,如果不处理程序就不能编译通过,同时 RuntimeException 是 Exception 子类。如常见的IOException、ClassNotFoundException等。

十大深恶痛绝的 Java 异常

1、NullPointerException

空指针异常,操作一个 null 对象的方法或属性时会抛出这个异常。具体看这篇文章:Java 避免空指针的 5 个案例

2、OutOfMemoryError

内存异常异常,这不是程序能控制的,是指要分配的对象的内存超出了当前最大的堆内存,需要调整堆内存大小(-Xmx)以及优化程序。

3、IOException

IO,即:input, output输入输出,我们在读写磁盘文件、网络内容的时候经常会生的一种异常,这种异常是受检查异常,需要进行手工捕获。
如文件读写会抛出 IOException:

  1. public int read() throws IOException{}
  2. public void write(int b) throws IOException{}

4、FileNotFoundException

文件找不到异常,如果文件不存在就会抛出这种异常。
如定义输入输出文件流,文件不存在会报错:

  1. public FileInputStream(File file) throws FileNotFoundException{}
  2. public FileOutputStream(File file) throws FileNotFoundException{}

FileNotFoundException 其实是 IOException 的子类,同样是受检查异常,需要进行手工捕获。

5、ClassNotFoundException

类找不到异常,Java开发中经常遇到,是不是很绝望?这是在加载类的时候抛出来的,即在类路径下不能加载指定的类。
看一个示例:

  1. public static <T> Class<T> getExistingClass(ClassLoader classLoader, String className) {
  2. try {
  3. return (Class<T>) Class.forName(className, true, classLoader);
  4. }
  5. catch (ClassNotFoundException e) {
  6. return null;
  7. }
  8. }

它是受检查异常,需要进行手工捕获。

6、ClassCastException

类转换异常,将一个不是该类的实例转换成这个类就会抛出这个异常。
如将一个数字强制转换成字符串就会报这个异常:

  1. Object x = new Integer(0);
  2. System.out.println((String)x);

这是运行时异常,不需要手工捕获。

7、NoSuchMethodException

没有这个方法异常,一般发生在反射调用方法的时候,如:

  1. public Method getMethod(String name, Class<?>... parameterTypes)
  2. throws NoSuchMethodException, SecurityException {
  3. checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
  4. Method method = getMethod0(name, parameterTypes, true);
  5. if (method == null) {
  6. throw new NoSuchMethodException(getName() + "."
  7. + name + argumentTypesToString(parameterTypes));
  8. }
  9. return method;
  10. }

它是受检查异常,需要进行手工捕获。

8、IndexOutOfBoundsException

索引越界异常,当操作一个字符串或者数组的时候经常遇到的异常。

深恶痛绝的 Java 异常 - 图2 如图所示,它是运行时异常,不需要手工捕获。

9、ArithmeticException

算术异常,发生在数字的算术运算时的异常,如一个数字除以 0 就会报这个错。

  1. double n = 3 / 0;

这个异常虽然是运行时异常,可以手工捕获抛出自定义的异常,如:

  1. public static Timestamp from(Instant instant) {
  2. try {
  3. Timestamp stamp = new Timestamp(instant.getEpochSecond() * MILLIS_PER_SECOND);
  4. stamp.nanos = instant.getNano();
  5. return stamp;
  6. } catch (ArithmeticException ex) {
  7. throw new IllegalArgumentException(ex);
  8. }
  9. }

10、SQLException

SQL异常,发生在操作数据库时的异常。
如下面的获取连接:

  1. public Connection getConnection() throws SQLException {
  2. if (getUser() == null) {
  3. return DriverManager.getConnection(url);
  4. } else {
  5. return DriverManager.getConnection(url, getUser(), getPassword());
  6. }
  7. }

又或者是获取下一条记录的时候:

  1. boolean next() throws SQLException{}

它是受检查异常,需要进行手工捕获。