扩展 - Exception 和 Error
# Exception 和 Error 有什么区别
Exception 和 Error 都继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出或者捕获异常。
Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,并进行相应处理
Error 是指在正常情况下,不大可能出现的情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 类,都是 Error 的子类。
Exception 又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源代码中必须显示地进行捕获处理,这是编译期检查的一部分,如 IOException。不检查异常就是所谓的运行时异常(程序导致的异常),类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以通过编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
# NoClassDefFoundError 和 ClassNotFoundException 的区别
NoClassDefFoundError 是一个错误,指在运行时 JVM 类加载器在 classpath 下找不到需要加载的类,可能的原因:
- 对应的 Class 在 java 的 classpath 中不可用,可打印出
System.getproperty(“java.classpath”)
- 可能用 jar 命令运行的程序,但类并没有在 jar 文件的 manifest 文件中的 classpath 属性中定义
- 可能程序的启动脚本覆盖了原来的 classpath 环境变量
- 因为 NoClassDefFoundError 是 java.lang.LinkageError 的一个子类,所以可能由于程序依赖的原生的类库不可用而导致,如依赖冲突
- 检查日志文件中是否有 java.lang.ExceptionInInitializerError 这样的错误,NoClassDefFoundError有可能是由于静态初始化失败导致的
ClassNotFoundException 是一个异常,指在编译时找不到类,可能原因:
使用反射方式在运行时动态加载类,例如使用 Class.forName 方法来动态地加载类时,可以将类名作为参数传递给上述方法从而将指定类加载到 JVM 内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出 ClassNotFoundException 异常
# 异常处理的基本原则
尽量不要捕获类似 Exception 这样的通用异常,而应捕获特定异常。除非深思熟虑否则不要捕获 Throwable 或者 Error,这样很难保证能够正确处理 OutOfMemoryError
不要生吞异常,尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里
Throw early, catch late 原则。
在保证诊断信息足够的同时,也要考虑避免包含敏感信息,一般不会输出具体的机器名、IP、端口,以及用户数据
请勿在 try 代码块中调用 return、break 或 continue 语句。万一无法避免,一定要确保 finally 的存在不会改变函数的返回值,因为 finally 的 return 会覆盖 try 中的返回值
# Checked Exception 是个错误设计
Checked Exception 的假设是我们捕获了异常,然后恢复程序。但是,其实我们大多数情况下,根本就不可能恢复。Checked Exception 的使用,已经大大偏离了最初的设计目的。
Checked Exception 不兼容 functional 编程,如果你写过 Lambda/Stream 代码,相信深有体会
# Java 的异常处理机制对性能的影响
- try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。
- Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。
# 带资源的 try 语句
假设资源属于一个实现了 AutoCloseable 接口的类,Java SE 7 为这种代码提供了一种快捷方式(try-with-resources)
try (Resource res = ...) {
work with res
}
2
3
当 try 块退出时会自动调用 res.close()