异常是指由于各种无法预知的情况,导致程序中断运行的一种指令流,如:文件找不到、非法参数、网络超时等。为了保证程序的正常运行,在设计程序时必须考虑到各种异常情况,并正确的对异常进行处理。异常也是一种对象,Java 当中定义了许多异常类,并且定义了基类 java.lang.Throwable 作为所有异常的超类。Java 语言设计者将异常划分为两类:Error 和 Exception, 其体系结构大致如下图所示:
Throwable 有可抛出的意思,是根基类。在 Java 中只有 Throwable 类型的实例才能被抛出(throw)或者捕获(catch),它是异常处理机制的基本类型。
Throwable 有两个重要的子类:Exception(异常)和 Error(错误),两者都包含了大量的异常处理类:
- Error(错误):是程序中无法处理的错误,表示运行应用程序中出现了严重的错误,此类错误一般表示代码运行时 JVM 出现问题。 通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当 JVM 耗完可用内存时,将出现 OutOfMemoryError。此类错误发生时,JVM 将终止线程。这些错误是不可查的,非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误;
- Exception(异常):程序本身可以捕获并且可以处理的异常;
Exception 这种异常又分为两类:运行时异常和编译异常
- 运行时异常(不受检异常):RuntimeException 类极其子类表示 JVM 在运行期间可能出现的错误。 比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理;
- 编译异常(受检异常):Exception 中除 RuntimeException 极其子类之外的异常。如果程序中出现此类异常,比如说 IOException,必须对该异常进行处理,否则编译不通过。 在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。
在 Java 应用中,异常的处理机制分为抛出异常和捕获异常:
- 抛出异常:当一个方法出现错误而引发异常时,该方法会将该异常类型以及异常出现时的程序状态信息封装为异常对象,并交给本应用。运行时,该应用将寻找处理异常的代码并执行。任何代码都可以通过throw关键词抛出异常,比如 Java 源代码抛出异常、自己编写的代码抛出异常等;
- 捕获异常:一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常。所谓合适类型的异常处理器指的是异常对象类型和异常处理器类型一致;
对于不同的异常,Java 采用不同的异常处理方式:
- 运行异常将由系统自动抛出,应用本身可以选择处理或者忽略该异常;
- 对于方法中产生的 Error,该异常一旦发生 JVM 将自行处理该异常,因此 Java 允许应用不抛出此类异常;
- 对于所有的可查异常,必须进行捕获或者抛出该方法之外交给上层处理。也就是当一个方法存在异常时,要么使用 try-catch 捕获,要么使用该方法使用 throws 将该异常抛调用该方法的上层调用者。
异常处理过程,一般情况下是用 try 来执行一段程序,如果系统会抛出(throw/throws)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码(finally)来处理。需要注意的是,try-catch 代码会产生额外的开销。
- try:用来指定一段可能产生异常的代码块,是一个“监控区域”的概念;
- catch:跟在 try 代码块后面,用来指定想要捕获的异常的类型;
- throw:语句用来明确地抛出一个异常;
- finally:为确保一段代码不管发生什么异常状况都要被执行;