两者的区别
- Exception 和 Error 都继承了 Throwable 类,只有 Throwable 类型的实例才可被抛出(throws)或者捕获(catch),它是异常处理机制的基本组成类型;
- Exception 和 Error 体现了Java 平台设计者对不同异常情况的分类:
- Exception 是程序正常运行中,可预料的意外情况,可以被捕获,并进行相应处理;
- Error 是指在正常情况下,不太可能出现的情况,绝大部分 Error 都会导致程序处于非正常、不可恢复的状态。常见有OutOfMemoryError之类的,都是 Error 的子类。
- Exception 可分为 可检查(checked) 异常和 不检查(unchecked)异常,可检查异常在源代码里必须显示地进行捕获处理。不检查异常就是所谓的运行时异常,类似 NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。诸如NoClassDefFoundError 和 ClassNotFoundException 有什么区别,需要理解。
异常的处理原则
异常处理的两个基本原则:
- 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。为了方便以后的理解,我们尽可能让自己的代码能直观地体现出尽量多的信息,直接Exception隐藏了我们的目的,且我们也要保证程序不会捕获到我们不希望捕获的异常。
- 不要生吞(swallow)异常。生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的。如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。
e.printStackTrace() : Prints this throwable and its backtrace to the standard error steam。标准出错(STERR)不是个合适的输出选项,因为你很难判断到底输出到哪里去了。尤其是分布式系统,如果发生了异常,但无法找到堆栈轨迹(stacktrace),就很难诊断错误。最好使用日志,输出详细错误到日志系统里。
捕获后的异常处理
如何处理呢?最差的处理方式是 “生吞异常”。如果实在不知道如何处理,可选择保留原有异常的 cause 信息,直接再抛出或者构建新的异常抛出去。
自定义异常
自定义异常需要考虑:
- 是否需要定义成 Checked Exception,这种类型设计的初衷就是为了从异常情况恢复;
- 保证诊断信息足够的同时,还需要考虑避免包含敏感信息。一些具体的机器名、IP、端口、用户数据等,一般不可以输出到日志里面。
性能开销
异常处理机制的在性能上的缺点:
- try-catch 代码段会产生额外的性能开销,所以建议仅捕获必要的代码段,尽量不要 try 一整段代码;另外,利用异常控制代码流程比我们用条件语句(if/else、switch)要低效;
- Java 每实例化一个 Exception,都会对当时的栈进行快照,这是相对比较重的操作,如果发生的非常频繁,开销就会很大。
当我们的服务出现反应变慢、吞吐量下降时,检查发生最频繁的 Exception 也是一种思路。