Java 将所有的错误封装为一个对象,其根本父类为 Throwable, Throwable有两个子类:Error 和 Exception。

Trowable 类中常用方法如下:
1 | // 返回异常发生时的详细信息 |
Error
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions.
Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error 的任何子类,因为这些错误可能是再也不会发生的异常条件。
调用 stop() 方法时会抛出 java.lang.ThreadDeath 错误,但在通常的情况下,此错误不需要显式地捕捉。不过方法 stop() 已经被作废,因为如果强制让线程停止则有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。
经典 Error 如下:
1 | OutOfMemoryError |
VirtulMachineError
有四种不同类型的 VirtulMachineError:
- OutOfMemoryError
- StackOverflowError
- InternalError
- UnknownError
OutOfMemoryError
OutOfMemoryError 有八种不同类型:
- java.lang.OutOfMemoryError:Java 堆空间
- java.lang.OutOfMemoryError:GC 开销超过限制
- java.lang.OutOfMemoryError:请求的数组大小超过虚拟机限制
- java.lang.OutOfMemoryError:PermGen 空间
- java.lang.OutOfMemoryError:Metaspace
- java.lang.OutOfMemoryError:无法新建本机线程
- java.lang.OutOfMemoryError:杀死进程或子进程
- java.lang.OutOfMemoryError:发生 stack_trace_with_native_method
触发每种错误的原因各有不同。类似地,根据 OutOfMemoryError 不同的问题类型,对应的解决方案也不一样。
StackOverflowError
线程的堆栈存储了执行的方法、基本数据类型值、局部变量、对象指针和返回值信息,所有这些都会消耗内存。当栈深度超过虚拟机分配给线程的栈大小时,那么就会抛出 java.lang.StackOverflowError。通常由于执行程序中有一个错误,在线程重复递归调用同一个函数时会发生这个问题。
InternalError
JVM 抛出 java.lang.InternalError 有三个原因,虚拟机软件出现错误、系统软件底层出现错误或者硬件出现故障。
一般极少会遇到 InternalError 这样的错误。要了解哪些特定情况可能导致 InternalError,请在 Oracle 的 Java Bug 数据库 中搜索 InternalError。
UnknownError
当发生异常或错误,但 Java 虚拟机无法报告确切的异常或错误时,就会抛出 java.lang.UnknownError。UnknownError 很少出现。事实上,在 Oracle Java Bug 数据库 中搜索 UnknownError 时,只找到了2个 Bug。
Bug ID: JDK-4023606 AppletViewer generates java.lang.UnknownError when loading inner class.
Bug ID: JDK-4054295 UnknownError while loading class with super_class equal to zero
AWTError
AWT(Abstract Window Toolkit),中文译为抽象窗口工具包,是 Java 提供的用来建立和设置 Java 的图形用户界面的基本工具。AWTError一般也很少用到。事实上,在 Oracle Java Bug 数据库 中搜索 AWTError 时,只找到了8个 Bug。
Exception
Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。Exception 分为未检查异常(RuntimeException)和已检查异常(非RuntimeException)。 未检查异常是因为程序员没有进行必需要的检查,因为疏忽和错误而引起的错误。几个经典的 RunTimeException 如下:
1 | NullPointerException |
- 可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。除了 Exception 中的 RuntimeException 及其子类以外,其他的 Exception 类及其子类(例如:IOException和ClassNotFoundException)都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
- 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。RuntimeException表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。
RuntimeException
运行时异常都是 RuntimeException 类及其子类异常,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是 Java 编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用 try-catch 语句捕获它,也没有用 throws 子句声明抛出它,也会编译通过。
非RuntimeException
非运行时异常(也称受检查的异常)是 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、SQLException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。
异常处理的机制
抛出异常
当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
任何 Java 代码都可以抛出异常,如:自己编写的代码、来自 Java 开发环境包中代码,或者 Java 运行时系统。无论是谁,都可以通过 Java 的 throw 语句抛出异常。从方法中抛出的任何异常都必须使用throws子句。
捕获异常
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
捕捉异常通过try-catch语句或者try-catch-finally语句实现。
异常的处理
1 | throws //直接往上一层抛出异常; |
throw 和 throws 两个关键字有什么不同
- throw 是用来抛出任意异常的,你可以抛出任意 Throwable,包括自定义的异常类对象;
- throws 总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。如果方法抛出了异常,那么调用这个方法的时候就需要处理这个异常。
try-catch-finally-return执行顺序
- 不管是否有异常产生,finally 块中代码都会执行;
- 当 try 和 catch 中有 return 语句时,finally 块仍然会执行;
- finally 是在 return 后面的表达式运算后执行的,所以函数返回值是在 finally 执行前确定的。无论 finally 中的代码怎么样,返回的值都不会改变,仍然是之前 return 语句中保存的值;
- finally 中最好不要包含 return,否则程序会提前退出,返回值不是 try 或 catch 中保存的返回值。
1 | // 按正常顺序执行。 |
异常链
在设计模式中有一个叫做责任链模式,该模式是将多个对象链接成一条链,客户端的请求沿着这条链传递直到被接收、处理。同样Java异常机制也提供了这样一条链:异常链。
我们有两种方式处理异常,一是 throws 抛出交给上级处理,二是 try…catch 做具体处理。try…catch 的 catch 块我们可以不需要做任何处理,仅仅只用 throw 这个关键字将我们封装异常信息主动抛出来。然后在通过关键字 throws 继续抛出该方法异常。它的上层也可以做这样的处理,以此类推就会产生一条由异常构成的异常链。
通过使用异常链,我们可以提高代码的可理解性、系统的可维护性和友好性。
同理,我们有时候在捕获一个异常后抛出另一个异常信息,并且希望将原始的异常信息也保持起来,这个时候也需要使用异常链。在异常链的使用中,throw 抛出的是一个新的异常信息,这样势必会导致原有的异常信息丢失,如何保持?在 Throwable 及其子类中的构造器中都可以接受一个 Throwable cause 参数,该参数保存了原有的异常信息,通过 getCause() 就可以获取该原始异常信息。
1 | public void test() throws XxxException{ |
注意
精确原则
- 尽可能的减小 try 块——try 块不要包含太多的信息,仅包所需。
- catch 语句应当尽量指定具体的异常类型,不要一个Exception试图处理所有可能出现的异常
不要做渣男,负点责
- 既然捕获了异常,就要对它进行适当的处理。不要捕获异常之后又把它丢弃。
- 在异常处理模块中提供适量的错误原因信息,使其后续易于理解和阅读。
- 保证所有资源都被正确释放。 ——充分运用finally关键词。或者使用 Java 提供的语法糖 try() catch
两不要
- 不要在 finally 块中处理返回值。
- 不要在构造函数中抛出异常。
异常使用指南
应该在下列情况下使用异常(From 《Think in java》)。
- 在恰当的级别处理问题(在知道该如何处理异常的情况下才捕获异常)。
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,以代替方法预计会返回的值。
- 把当前运行环境下能做的事情尽量做完。然后把相同(不同)的异常重新抛到更高层。
- 终止程序。
- 进行简化。
- 让类库和程序更加安全。(这既是在为调试做短期投资,也是在为程序的健壮做长期投资)