本文参考自https://zhuanlan.zhihu.com/p/332320734

概要

对于一个合格的Java程序员来说,在自己的代码逻辑中使用try…catch来进行异常处理是非常常见且必要的事情,因为它让你的程序更加健壮稳定。

异常分类

首先,我们要清楚为什么要写try…catch,根本原因在于程序会出现可能的问题,而这个问题是指:阻止当前方法或者作用域继续执行的问题它会阻止你的程序沿着你预先编写的逻辑继续往下运行。所有的这种可能出现的问题在Java中统一叫做Throwable。而Throwable又可以归为2大类: Error 和Exception,

image-20230827203158883

Error

其中,Error也叫错误,这部分是程序员无法处理的,很多情况下try…catch了也没有用,程序依然会crash退出,因为这部分属于Java虚拟机异常,跟你代码逻辑无关(其实还是有关的,可能是你代码设计有问题,比如程序中有很深的递归调用导致StackOverFlowError;数据处理时用有限的内存处理了太多的数据导致OutOfMemoryError等),这部分出现的异常最大的特点是:异常出现时,程序员无法用常规的try…catch或者throws方式进行处理,因为无法处理。而对于这部分的问题,程序员只能在程序设计、运行环境以及运行参数上去尽量规避。

Exception

而对于程序员来说,能施展你对程序异常处理才华的部分,就只能是程序的Exception部分了,而对于这部分又分为运行时异常(RuntimeException)和非运行时异常(非RuntimeException)。其中,RuntimeException又叫非检查异常,非RuntimeException又叫检查异常。如下图所示:

image-20230827203409346

检查型异常

表示在正常的程序运行期间可能发生的预期问题,该部分异常是程序要求你必须处理的,否则编译无法通过。比如当你用程序尝试跟外部IO设备进行通信时,可能会发生的IO异常现象,该部分异常的发生是不可避免的,由外部因素决定,程序本身无法杜绝这种情况下出现的异常就叫做检查性异常也就是你在代码中不得不处理的异常。

非检查性异常

表示不需要进行强制处理的异常,只会发生在程序运行时,该部分异常的产生多半是因为数据的缘故导致,之所以JVM没有强制性要求程序员去处理,因为该部分的异常是程序员可以杜绝的(可预先进行条件判断处理,增加代码的健壮性)。比如常见的NullPointerException、ClassCastException,因为是非检查异常,即便程序员不做任何处理程序也是可以通过编译的。

如何规范使用try…catch

对于以上提到的各种异常情况(Exception),该如何处理呢。应该秉承以下几个原则:

对检查性异常而言

  • 如果明确知道如何处理异常,比如捕获异常后,执行回滚机制,重试机制等,可进行try…catch操作;
  • 如果不清楚当前异常如何处理,则直接进行throws,把异常交给调用层处理。一般不建议把异常try…catch之后只是仅仅把方法的调用栈打印出来,这样不能叫做真正意义的异常处理;
  • 如果要try的代码块,与后续要执行的代码逻辑是解耦的(即该部分调用是否有异常跟后面要运行的代码逻辑没有关系),为了避免该部分因为运行异常导致的程序crash退出,可将该部分进行try…catch;

对于非检查性异常而言:

  • 尽量使用条件判断来代替可能的try…catch操作,比如如下的try…catch逻辑:
1
2
3
4
5
6
7
8
public void fun3(){
Object obj = fun1();
try {
obj.fun2();
} catch (NullPointerException e) {
// 处理逻辑
}
}

可以优化为

1
2
3
4
5
6
7
8
9
public void fun3(){
Object obj = fun1();
if (null == obj){
// 处理逻辑
}
else{
obj.fun2();
}
}
  • 在对可能出现异常的代码进行try…catch时,尽可能避免将无关代码加入到try…catch体内,比如:
1
2
3
4
5
6
7
8
9
public void fun3(){
try {
int a = 10;//不会抛出异常
String b = fun2();//不会抛出异常
Object obj = fun1();
} catch (Exception e) {
//处理逻辑
}
}

应该为:

1
2
3
4
5
6
7
8
9
public void fun3(){
int a = 10;//不会抛出异常
String b = fun2();//不会抛出异常
try {
Object obj = fun1();
} catch (Exception e) {
//处理逻辑
}
}
  • 如果不清楚当前写的代码逻辑是否会有运行时异常抛出,则不要盲目的进行try…catch或者throws,在代码进行测试时遇到了可能的异常后,再针对具体的异常进行异常处理;
  • 不要滥用try…catch,一定只对确定的需要进行异常捕获的代码块进行try…catch,try…catch体中的代码量越少越好,尤其不要搞嵌套的try…catch;

try…catch的优缺点总结

凡事有利就有弊,try…catch在异常处理方面给程序员带来的便捷(将正常情况下的程序处理逻辑和发生异常后的程序处理逻辑分开)的同时,也同样会带来一些不便,对于try…catch的优缺点,总结如下:

优点:

  • 对可能出现的预期的异常,可将程序恢复到某个已知的稳定点上;
  • 对可预见性的异常,可进行重试机制或者实施替代方案进行业务补救措施,避免业务损失;
  • 避免了因为一些无关紧要的异常(不影响程序的执行结果),导致的程序直接crash退出;

缺点:

  • 让代码的可读性变差,尤其是频繁大量的try…catch使用情况下;
  • try…catch体内的代码块,编译器在运行时很难进行优化处理,某种程度上削弱了代码执行效率;
  • 异常一旦发生,对其进行捕获的过程是非常消耗系统资源的,会降低程序的执行效率(因此,能用条件判断解决的就一定不要用try…catch);