CH06-异常机制
概览
Java 异常是 Java 提供的一种识别及响应错误的机制,Java 异常机制可以使程序中异常处理的代码和正常业务代码分离,保证程序代码更加优雅,并提高程序的健壮性。
层次结构
异常是指非预期的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java 通过 API 中 Throwable 类的众多子类描述各种不同的异常。因此,Java 异常都是对象,是 Throwable 子类的实例,描述了出现在一段编码中的错误条件。当条件生成时,错误将引发异常。
Throwable
Throwable 是 Java 语言中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
Error
Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照 Java 惯例,我们是不应该实现任何新的 Error 子类的!
Exception
程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
- 运行时异常
- 都是 RuntimeException 类及其子类异常,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
- 运行时异常的特点是 Java 编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用 try-catch 语句捕获它,也没有用 throws 子句声明抛出它,也会编译通过。
- 编译期异常
- 是 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、SQLException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。
受检异常/非受检异常
- 受检异常:编译器妖气必须处理的异常
- 正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。
- 除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于受检异常。这种异常的特点是 Java 编译器会检查它,也就是说,当程序中可能出现这类异常,要么用 try-catch 语句捕获它,要么用 throws 子句声明抛出它,否则编译不会通过。
- 非受检异常:编译器不强制要求检查的异常
- 包括运行时异常(RuntimeException与其子类)和错误(Error)。
异常基础
关键字
- try:监听异常
- catch:捕获异常
- finally:总是执行
- throw:抛出异常
- throws:声明异常
异常捕获
- try-catch
- try-catch-finally
- try-finally
- try-with-resource: AutoCloseable
常见异常
- RuntimeException
- ArrayIndexOutOfBoundsException:数组索引越界
- ArithmeticException:除零异常
- NullPointerException:空指针
- ClassNotFoundException:类加载
- NegativeArraySizeException:数组长度为负
- ArrayStoreException:数组元素类型不兼容
- SecurityException:安全性异常
- IllegalArgumentException:非法参数
- IOException
- IOException:输出输出流异常
- EOFException:文件结束
- FileNotFoundException:文件未找到
- Others
- ClassCastException
- SQLException
- NoSuchFieldException
- NoSuchMethodException
- NumberFormatException
- StringIndexOutOfBoundsException
- IllegalAccessException
- InstantiationException
应用实践
- 在恰当的级别处理异常,即知道该如何处理的地方及时处理异常。
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,以代替方法预计会返回的值。
- 把当前运行环境下能做的事尽量做完,然后把相同的异常重抛到更高层。
- 终止程序。
- 进行简化。
- 让类库和程序更安全。
- 不要通过异常来实现业务流程。
底层机制
try-catch
public static void simpleTryCatch() {
try {
testNPE();
} catch (Exception e) {
e.printStackTrace();
}
}
通过 javap 的带编译后的代码:
//javap -c Main
public static void simpleTryCatch();
Code:
0: invokestatic #3 // Method testNPE:()V
3: goto 11
6: astore_0
7: aload_0
8: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
11: return
Exception table:
from to target type
0 3 6 Class java/lang/Exception
异常表中包含了一个或多个异常处理器的信息,内容如下:
- from:可能发生异常的起始点
- to:可能发生异常的结束点
- target:从 from 到 to 之间发生异常后异常处理器的位置
- type:异常处理器所处理的异常类型
当发生一个异常时:
- JVM 会在当前出现异常的方法中查找异常表,检查是否有合适的处理器
- 如果当前方法的异常表不为空,且异常符合表中 from to 的范围,同时 type 匹配,则 JVM 将调用位于 target 的处理器来处理异常
- 如果上一步没有找到处理器,则继续检查异常表中的下一项
- 如果当前异常表无法处理,则向上查找(弹栈)调用方法的调用点,并重复 1-3 步的操作。
- 如果所有栈帧都被弹出,仍然没有处理,则抛出异常给当前 Thread,Thread 会终止。
- 如果当前 Thread 为最后一个非守护线程,且异常未处理,则导致 JVM 终止运行。
try-catch-finally
public static void simpleTryCatchFinally() {
try {
testNPE();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Finally");
}
}
通过 javap 得到异常表部分:
public static void simpleTryCatchFinally();
Code:
0: invokestatic #3 // Method testNPE:()V
3: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
6: ldc #7 // String Finally
8: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
11: goto 41
14: astore_0
15: aload_0
16: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
19: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
22: ldc #7 // String Finally
24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: goto 41
30: astore_1
31: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
34: ldc #7 // String Finally
36: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: aload_1
40: athrow
41: return
Exception table:
from to target type
0 3 14 Class java/lang/Exception
0 3 30 any
14 19 30 any
- 如果 0-3 之间发生了 Exception 异常,调用 14 位置的处理器。
- 如果 0-3 之间无论发生哪种异常,都调用 30 位置的处理器。
- 如果 14-19(catch) 之间无论发生什么异常,都调用 30 位置的处理器。
注意异常表会代码中 finally 的部分同时在异常表的 catch 和 finally 部分注册,以实现无论是否发生异常都执行的逻辑。
catch 先后顺序
先 catch 类层级较高的异常类型会导致编译错误。
return 与 finally
public static String tryCatchReturn() {
try {
testNPE();
return "OK";
} catch (Exception e) {
return "ERROR";
} finally {
System.out.println("tryCatchReturn");
}
}
这里无论是否抛出一查,finally 部分都会执行。
性能损耗
创建一个异常对象是创建一个普通对象所需耗时的几十倍,抛出、捕获异常则是创建异常对象所需耗时的数倍。
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.