问题
在开始本文之前,尝试回答如下问题
- NoClassDefFoundError 和 ClassNotFoundException 有什么区别
- 什么是 checked exception 怎么看?
- 什么异常不应该捕获?什么时候抛出异常( throw),什么时候声明抛出异常( throws),什么时候捕获之后重新抛出?
- try with resurce、try catch finally,multiple catch 分别指什么?用在什么场景?
- 捕获异常要注意的地方
- 异常日志如何记录,应不应该打印堆栈
- 异常处理最佳实践:全局异常避免堆栈暴露
- 什么是异常控制流程,举例说明
- 哪些异常不需要捕获,哪些异常要捕获,例外情况是什么?
- 哪些是敏感异常,敏感异常如何处理
- 你所在的团队是否有异常处理规范
- 异常的性能问题及原理
- 多线程下的异常处理?对于线程池注意Runntimeexception导致的线程逃逸现象。UnCaughtExceptionHandler
- 不同编程范式下的异常处理,反应式编程,lambda/stream
目标
掌握 Java 异常
为什么需要异常
如果没有异常,我们需要在每个函数都判断成功或失败,有了异常,只需要在出现错误对方抛出,在可以处理异常的地方捕获异常即可。代码明显比用错误码的方式要简洁很多。写过 C, Go 的应该都深有体会。比如 go 2.0 已经计划引入异常便是最好的明证。
异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。
异常之所以是一种强大的调试手段,在于其回答了以下三个问题:
- 什么出了错
- 在哪出的错
- 为什么出错
在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。
异常处理原则
有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是:
- 具体明确
- 提早抛出(throw early)
- 延迟捕获(catch late)
具体明确
Java 定义了一个异常类的层次结构,其以 Throwable 开始,扩展出 Error 和 Exception,而 Exception
又扩展出 RuntimeException。
这四个类是泛化的,并没有提供多少出错信息,虽然实例化这几个类是语法上合法的(如 new Throwable()), 但是最好还是把它们当虚基类看,使用它们的子类。Java 已经提供了大量异常子类,如需更加具体,你也可以定义自己的异常类。
包中定义了 Exception 类的子类 IOException,IOException 包括更加具体如 FileNotFoundException、EOFException、SocketException、 ObjectStreamException 等子类。每一种都描述了一类特定的I/O 异常。异常越具体,我们的程序就能更好地回答“什么出了错”这个问题。
提早抛出
异常堆栈信息提供了异常调用链的精确顺序,包括。详细记录了什么出了错(提供了非法参数值)、为什么出错(比如文件名不能为空值)、以及哪里出的错(每个方法调用的类名,方法名,文件名甚至行数),以此来精确定位异常现场。
通过在检测到错误时立刻抛出异常(迅速失败),可以有效避免不必要的对象构造或资源占用。比如文件或网络连接资源,同样,打开这些资源所带来的清理操作也可以省略。比如
public test(String fileName) {
if (isVaildFileName(fileName)) {
throw new IllegalArgumentException("file is not vaild");
}
//创建资源
//IO 操作
}
这里将文件名的判断提前,而不是在打开资源的之后,在具体执行操作的时候才处理。
延迟捕获
异常的处理中非常容易犯的一个错误是:在程序有能力处理异常之前就捕获它。出现异常自然而然的做法就是立即将代码用try块包装起来,并使用catch捕获异常,以免编译器报错。Java 中检查
异常必须被捕获或抛出助长了这种错误行为。
问题在于,捕获之后该拿异常怎么办?最不该做的就是什么都不做。空的 catch 块等于把整个异常丢进黑洞,能够说明何时何处为何出错的所有信息都会永远丢失。把异常写到日志中还稍微好点,至少还有记录可查。但我们总不能指望用户去阅读或者理解日志文件和异常信息。
捕获异常之后处理的标准:
- 如果你的方法无法胜任,那么就不要处理异常,把它留到后面捕获和在恰当的层面处理。
- 在合适的层面捕获异常,以便你的程序要么可以从异常中有意义地恢复并继续下去,而不会导致更深入的错误。
- 为用户提供明确的信息,包括引导他们从错误中恢复过来。
总之,一句话,如果你不知道怎么处理,就不要处理
结论
经验丰富的开发人员都知道,调试程序的最大难点不在于修复缺陷,而在于从海量的代码中找出缺陷的藏身之处。只要遵循本文的三个原则,就能让你的异常协助你跟踪和消灭缺陷,使你的程序更加健壮,对用户更加友好。