异常的分类
假如你开车上山,车坏了,你拿出工具箱修一修,修好继续上路(Exception被捕获,从异常中恢复,继续程序的运行),
车坏了,你不知道怎么修,打电话告诉修车行,告诉你是什么问题,要车行过来修。
(在当前的逻辑背景下,你不知道是怎么样的处理逻辑,把异常抛出去到更高的业务层来处理)。
你打电话的时候,要尽量具体,不能只说我车动不了了。那修车行很难定位你的问题。(要捕获特定的异常,不能捕获类似Exception的通用异常)。
还有一种情况是,你开车上山,山塌了,这你还能修吗?(Error:导致你的运行环境进入不正常的状态,很难恢复)
参看图:
Exception和Error都是继承了Throwable类,在Java中也只有Throwable类型的实例才可以被抛出或者捕获
Error描述了Java运行时系统内部错误和资源耗尽错误,不需要也不便于捕获。
Exception分为检查型(checked)异常和非检查型(unchecked)异常
checked异常必须显式进行捕获处理,属于编译期检查的一部分
unchecked异常就是运行时异常。
2、基本语法
try-catch-finally块,throw,throws关键字
在finally里做一些资源回收工作
try-with-resources,multiple catch
或自定义扩展AutoCloseable或者Closeable的对象
3、捕获异常的基本原则
1)、尽量不要捕获类似Exception这样通用异常,应该捕获特定异常。
原因:这样的异常捕获不能反应更多的信息
2)、不要生吞异常
原因:导致问题诊断变得麻烦
printStackTrace()输出(STERR),无法确定输出到哪里,最好使用产品日志,详细输出到日志系统
3)Throw early,catch late原则
public void readPreferences(String fileName){ //...perform operations... InputStream in = new FileInputStream(fileName); //...read the preferences file... }
如果fileName是null,那么程序就会抛出NPE,需要复杂定位,在发现问题的时候,第一时间抛出
能够更清晰反应问题
这里让问题“throw early”
public void readPreferences(String filename) { Objects. requireNonNull(filename); //...perform other operations... InputStream in = new FileInputStream(filename); //...read the preferences file.. }
至于"catch late" 如果不知道如何处理可以保留原异常cause信息,或者再抛出或者构建新的异常抛出去。
自定义异常应该保证诊断信息足够的同事,避免敏感信息。
4、异常的性能问题
try-catch代码段会产生额外的性能开销,原因是影响JVM对代码的性能调优,尽量不要用大的try包住整段的代码
利用异常控制代码流程,也不是个好主意,比条件语句(if/else,switch)要低效。
java每实例化一个Exception,就会对当时栈进行快照,如果发生比较频繁,开销就比较大
5、异常进阶
异常:这种情况下的异常,可以通过晚上任务充实机制,在执行异常时,保存当前任务信息加入重试队列,重试的策略
根据业务需求决定,当达到重试上限依然无法成功,记录任务执行失败,同时发出警告。
日志:类比消息中间件,处在不同线程之间的同一个任务,简单高效做法是用traceId/requestId串联,有些日志支持MDC/NDC功能,可以串联相关联日志
6、阿里规约看异常