对比 Exception 和 Error,另外,运行时异常与一般异常有什么区别?
- Exception 和 Error 都是继承了 Throwable类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或捕获(catch),它是异常处理机制的基本组成类型。
- Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
- Error 是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序处于非正常、不可恢复状态,不需要进行捕获。
- Exception 又分为可检查(checked)异常和不检查(uncheked异常)/RuntimeException,Checked Exception在源代码里必须显示地进行处理,是编译期检查的一部分。RuntimeException通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,不会在编译期强制要求。
理解 Throwable、Exception、Error 的设计和分类
理解 Java 语言中操作 Throwable 的元素和实践
使用try-with-resources 和 multiple catch,在编译时期,会自动生成相应的处理逻辑。try-with-resources自动按照约定俗成 close 那些扩展了 AutoCloseable或者Closeable的对象。
try (BufferedReader br = new BufferedReader(…);
BufferedWriter writer = new BufferedWriter(…)) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
// Handle it
}
最佳实践
-
尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定的异常。
- 隐藏了我们的目的
- 可能捕获到我们不希望捕获的异常,例如RuntimeException
- 不要捕获Throwable 或者 Error,这样很难保证我们能够正确处理OutOfMemoryError之类的错误。
-
不要生吞(swallow)异常
- 掩盖问题,可能会导致非常难以诊断的诡异情况。
- 线上不允许e.printStackTrace(),很难判断出到底输出到哪里去了
Throw early, catch late 原则
让问题“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 信息,直接再抛出或者构建新的异常抛出。在更高层面,因为有了清晰的(业务)逻辑,往往会更清楚合适的处理方式是什么。
- 自定义异常需要考虑问题
- 是否提供足够的异常信息
- 是否需要定义成Checked Exception,这种类型设计的初衷更是为了从异常情况恢复
- 在保证诊断信息足够的同时,也要考虑避免包含敏感信息,因为那样可能导致潜在的安全问题。
性能角度看异常处理机制
- try-catch 代码段会产生额外的性能开销,往往会影响JVM对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码。
- 不要使用异常控制代码流程,比条件语句要低效,语义不明确。
- Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作,如果异常非常频繁,这个开销就不能忽略。
- 当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的Exception也是一种思路。