- Exception和Error都继承了Throwable类,在java中,只有Throwable类型的实例才能被抛出或者捕获,它是异常处理机制的基本组成部分;
- Exception是程序正常运行中,可预料的情况,应该被捕获,进行相应的处理;
- Error是指在正常情况下不大可能出现的情况,绝大多数的Error都会导致程序处于非正常的、不可恢复的状态,既然是非正常情况,所以不便于也不需要捕获,如OutOfMemoryError是Error的子类;
- Exception又分为可检查异常与不检查异常,可检查异常必须在代码中显示的进行处理(捕获或者抛出去),这是编译器检查的一部分;不检查异常就是所谓的运行时异常,通常是可以编码避免的逻辑错误,具体根据需要来判断是否捕获,不会在编译期强制要求,如:ArrayIndexOutOfBoundException,ClassCastException,NullPointerException,IllegalArgumentException,ConcurrentModificationException, NoSuchElementException等等;
- NoClassDefFoundError :当JVM加载一个类时,如果该类在编译时是可用的,但在运行时候找不到这个类的定义的时候,JVM就会抛出NoClassDefFoundError;
- ClassNotFoundException:当JVM尝试用类加载器去加载一个类,但在classpath却找不到指定的类时,则会抛出ClassNotFoundException,典型的情况是使用Class.forName或者ClassLoader.loadClass()函数在运行时加载类,却无法在classpath中找到类定义;
- 异常处理代码比较繁琐,需要写千篇一律的捕获代码,在finally里面做一些资源回收工作,java语言在1.7中引入了一些更加便利的特性,try-with-resources和multiple catch,在编译期会自动生成相应的逻辑,自动按照约定俗成close那些扩展了Closeable和AutoCloseable的对象,属于语法糖;
try(BufferedReader br = new BUfferedRead(...);BufferedWriter bw = new BufferedWriter (...)){
} catch(IOException | OtherException e) {
}
- 异常处理的两个基本原则:
- 不要捕获泛泛的Exception之类,因为我们有义务让自己的代码能够直观的地体现更多的信息,而捕获Exception隐藏了我们的目的,另外我们不想捕获到我们不希望捕获的异常,比如我们可能更希望RuntimeException被扩散出来,而不是被捕获;
- 不要生吞异常,如果我们不把异常抛出来,也没有将信息输出到日志中,后续代码可能以不可控的方式结束运行,没人能够轻易判断程序是哪里产生了异常,以及是什么原因产生的异常;
- Throwable类的printStackTrace函数是将信息输出到标准出错,复杂的生产环境很难判断输出到哪里去了,最好输出到日志中,而不是使用printStackTrace;
- 异常处理需要遵循throw early,catch late原则,如果我们不知道该如何处理异常,可以考虑将异常抛出去,或者保留原有异常的cause信息,在构建新的异常抛出去,在更高层面,有了更清晰的逻辑,往往更清除合适的处理方式;
- 自行设计异常时,有两个方面需要考虑,第一是是否设计为CheckedException,作为异常的设计者,往往有足够的信息来分类;第二是注意保证诊断信息充足的同时,注意敏感信息,因为那可能存在信息安全问题;如java标准类库中,java.net.ConnectException,出错信息是类似于 Connect refused 之类的,不包含具体的机器地址,端口等信息,类似的情况在日志中也应该注意,一般用户数据是不允许输出到日志中的;
- 不要用try catch代码块来控制代码的流程,这比传统的if语句要低效,try往往会影响jvm对代码执行的优化,尽量不要使用一个大的try保住整段代码,仅捕获有必要的代码;
- 最后,每创建一个Exception,都会对当时的栈进行快照,这是一个比较重的操作,如果发生的非常频繁,那么开销就不可能被忽略了;追求极致性能的程序可以考虑创建不进行栈快照的Exception,但这就会失去堆栈信息,在大规模项目中,开发人员可能无法预料未来是否需要堆栈信息;当程序出现反应变慢,吞吐量下降时,检查发生最频繁的Exception也是一种思路;