1)Exception和Error的区别
Exception和Error都继承了Throwable类,只有Throwable类的实例才会被throw或catch。
他们都体现了Java平台设计者对不同异常情况的分类。
• Error是正常情况下不大出现的错误。大部分Error都不需要捕获,Error是不可查的。常见的OutOfMemoryError等都是Error的子类。
• Exception分为可检查和不检查。可检查在源代码中必须显式进行处理,是编译器检查的一部分。不检查就是运行时异常,如IndexOutOfBoundsException、NullPointerException等是编码可避免的,具体根据需求判断是否需要捕获。不在编译时要求。
我们可以从异常中恢复程序,但不应该在错误中恢复程序。
注:可检查异常不兼容Functional编程,如:Lamdba/Stream。
2)ClassNotDefFoundError和ClassNotFoundException的区别
NoClassDefFoundError:
如果JVM或ClassLoader实例尝试加载子类时找不到该类定义。
即要查找的类在编译时存在,运行时不存在,就会导致该错误。
可能是打包时漏掉了部分类或jar包出现损坏篡改。
解决办法是查找那些在开发中存在类路径下,但在运行时不在类路径下的类。
ClassNotFoundException:
常见问题是类名书写错误。
类名作参数传给Class.forName()方法动态加载到内存,该类在类路径没找到。
解决该问题需确保所需的类连同他依赖的包存在类路径中。
(还有个原因是当一个类已被类加载器加载到内存中,此时另一个类加载器又尝试动态从同一包中加载该类。通过控制动态类加载过程可避免上述情况发生)
3)throws和throw的区别
throws在方法头,throw在方法体。
throws是可能抛出异常,throw是一定抛出异常。
throws后可跟多个异常类名,多个用逗号分隔,throw是开发者主动抛出一个异常对象。
两者都是处理异常的方式,只是抛出和可能抛出。在没有try{}catch{}的情况下不会由函数处理异常,真正处理异常的是函数的上层调用。
4)这段代码的毛病
try {
// 业务代码
Thread.sleep(1000L);
} catch (Exception e) {
// Ignore it
}
解析:
· 尽量不要捕获Exception这种通用异常,应捕获特定异常。
· 日常开发读代码更多,要让代码体现出尽量多的信息。
· 泛泛的Exception等类,恰恰隐藏了我们的目的。
· 另外也要保证不会捕获到我们不希望捕获的异常。
(如:我们可能更希望RuntimeException被扩散出来,而不是捕获)
5)这段代码的毛病
try {
// 业务代码
} catch (IOException e) {
e.printStackTrace();
}
解析:
· 不要生吞Exception。如果不把异常抛出或输出到日志,程序可能在后续以不可控的方式结束。
· 没人能轻易判断是哪里抛出的Exception,以及是什么原因产生了Exception。
6)try{}catch{}代码块
· try{}catch{}会产生额外的性能开销,影响JVM对代码进行优化。
· 通常仅捕获有必要的代码段,不要一个大try包住整段代码。
· 用异常控制代码流程不是很好,比(if/else、swich)要低效。
7)异常实例的构造
JVM会在发生异常的地方记录栈轨迹(Strack Trace)。会逐一访问当前线程的栈帧,并记录各种调试信息,包括栈帧指向的方法名、方法所在类名、文件名、以及第几行代码发生异常。
8)对追求高性能的底层类库,Exception会增加诊断难度
有种方式是尝试不创建进行栈快照的Exception。这存在争议。因为前提是开发知道创建异常时未来是否需要堆栈。小项目或许可能,大项目可不是理智的选择。如果需要堆栈,但又没收集这些信息,复杂情况会大大增加诊断难度。如:微服务这种分布式系统。
9)不同的编程范式会影响异常处理策略,如反应式编程(Reactive Stream),其本身是异步、基于事件机制。出现异常决不能简单抛出。另外,由于代码堆栈不是同步调用的垂直结构,异常处理和日志要更加小心。我们看到的往往是特定的executor的堆栈,而不是业务方法调用关系。这种情况,解决思路是:
业务功能模块分配ID,在记录日志时将前后模块的ID进行调用关系的串联,一并跟任务信息进行日志保存。
往期精彩文章:
更多分享,可关注公众号「 桂圆金宝宝 」。