这个世界上有永远不会出错的程序吗?小伙子,你活在梦里呢。所以对于一门现代的高级语言,一般都会设计一套完善的异常处理机制,Java 正是其中之一,通过异常处理机制,可以大大的降低编写和维护可靠程序的门槛。so 今天的主题,我们就来讨论一下 Java 中 Exception 和 Error 的区别。
面试官:请说说 Exception 和 Error 的区别,另外,运行时异常和一般异常有啥区别?
经典回答
首先,Exception 和 Error 都继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被捕获(catch)或者是抛出 (throw), 它是异常处理机制的基本组成。
Exception 和 Error 体现了 Java 设计者对不同类型的分类。Exception 通常是程序正常运行,可以被预料的异常,可以被捕获并进行相应处理的。而 Error 指的是正常情况下不大可能会出现的异常,绝大多数 Error 都会导致程序处于非正常的,不可恢复的状态。它也是不便于也是不需要被捕获的。比如说比较经典的 OutOfMemoryError
这种 Error 的子类。
还需要特别说明的是,Exception 又分为可检查异常和不检查异常两类。
对于可检查异常,我们在代码需要显式的去捕获,并作出相应的处理,它属于编译检查的一部分。而不检查异常呢,类似 NullPointerException
, ArrayIndexOutOfBoundsException
之类,我们是可以在程序中进行判断来避免的,所以编译期并不会强制要求你去捕获。
下面给出 Exception, Error 的继承关系图,和常见的一些异常:
知识拓展
对于异常,我们因秉持的两个编码原则
-
1.尽量捕获特定的异常,而不是捕获 Exception 这样通用的异常:
try { // 业务代码 // … Thread.sleep(1000L); } catch (Exception e) { // Ignore it }
上面这段代码就违反了第一条原则,Thread.sleep(1000L)
会抛出 InterruptedException
,我们应该捕获它,而不是捕获通用的 Exception 类异常。要知道,日常工作中,我们读代码的时间要超过写代码的时间的,读别人写的代码,如果可维护性很差,你一定觉得特别痛苦,甚至是无从下手。所以,我们自己的编码时,应尽量让我们的代码直观的展现出更多的信息, 而泛泛的 Exception 实际上隐藏了我们原始的目的。
进一步来说,除非你经过深思熟虑了,确实要捕获 Exception 和 Error, 才去做这样的工作。
- 2.不要生吞异常:
线上系统中,最忌捕获了却啥事不做的情况发生,导致系统发生错误后,无法定位到问题发生在哪里,如下面这段代码:
try {
// 业务代码
// …
} catch (Exception e) {
// 什么事都不做
}
你至少需要打印错误日志,才能保证发生问题了,有迹可寻,能够快速的定位到问题。
对于异常,我们还应秉持 Throw early, catch late 原则:
public void readPreferences(String fileName){
//...perform operations...
InputStream in = new FileInputStream(fileName);
//...read the preferences file...
}
上面这段代码健壮性是不够的,你无法保证别人传递进来的 fileName 是否为空,若是 null, 就会抛出 NullPointerException, 由于没有第一时间抛出,导致打印出的堆栈信息往往很费解,这无疑增加的锁定问题的时间成本。如果我们能够秉持 Throw early 的原则:
public void readPreferences(String filename) {
Objects. requireNonNull(filename);
//...perform other operations...
InputStream in = new FileInputStream(filename);
//...read the preferences file...
}
这样我们就能第一时间抛出 NullPointerException
,而没有额外的错误堆栈输出,排查问题也能够快速定位到。
从性能角度审视 Java 异常处理机制
Java 的异常处理机制有两个比较昂贵的地方:
-
try-catch
代码段会产生额外的性能开销,就是说它会影响 JVM 对代码的进行性能优化,所以这里仅建议你捕获有必要捕获的代码片段,不要用一个大的 try 包住整个代码段;同时,利用异常控制代码流程也不是一个好的方案,它远比我们使用if-else
,switch
要低效。 -
Java 每实例化一个 Exception, 都会对当时的栈进行快照,这是一个很重的过程。如果程序中发生频繁,那开销就不能被忽略了。
所以,当我们发现服务出现反应变慢,吞吐量下降时,排查问题时,检查最频繁的 Exception 也是一种思路。
总结
今天主要从经典的面试题,Exception 和 Error 的区别为切入点,介绍了两者的区别。以及 Exception 的可检查异常和不检查异常。另外还有我们在处理异常时,需要秉持的两条原则。最后,我们又从性能的角度审视了 Java 的异常处理机制,以便我们能够编写出更加高效的代码。