前言
Java中一个大家熟知的知识点就是异常捕获,try…catch…finally组合,但是很多人不知道这里面有一个关于Java的缺陷,或者说是异常实现的一点不足之处。
我这边就通过一个很简单的实验给大家演示下效果玩玩儿,希望大家能觉得有趣。
模拟
1、自定义异常
这里,我们首先写一个自定义业务异常,专门用来抛出。
/**
* <p>
* 自定义业务异常
* </p>
*
* @author 程序员济癫,公众号:【Java分享客栈】
* @since 2022/12/6 19:20
*/
public class BusinessException extends RuntimeException {
public BusinessException() {
super();
}
public BusinessException(String errMsg) {
super(errMsg);
}
public BusinessException(String errMsg, Throwable throwable) {
super(errMsg, throwable);
}
}
复制代码
2、模拟异常
然后,我们写个测试方法来捕获并抛出空指针异常。
/**
* <p>
* 测试异常缺陷
* </p>
*
* @author 程序员济癫,公众号:【Java分享客栈】
* @since 2022/12/6 19:22
*/
@Slf4j
public class ExceptionTest {
public static void main(String[] args) {
try {
UserInfo userInfo = null;
System.err.println(userInfo.getName());
} catch (Exception ex) {
throw new BusinessException("捕获try的异常: " + ex.getMessage());
}
}
}
复制代码
看下效果,OK没问题。
接下来,我们加上finally看看。
/**
* <p>
* 测试异常缺陷
* </p>
*
* @author 程序员济癫,公众号:【Java分享客栈】
* @since 2022/12/6 19:22
*/
@Slf4j
public class ExceptionTest {
public static void main(String[] args) {
try {
UserInfo userInfo = null;
System.err.println(userInfo.getName());
} catch (Exception ex) {
throw new BusinessException("捕获try的异常: " + ex.getMessage());
} finally {
System.err.println("----------finally最终执行---------");
}
}
}
复制代码
看下效果,OK也没问题。
接下来我们这么做,在finally中抛出一个异常。
/**
* <p>
* 测试异常缺陷
* </p>
*
* @author 程序员济癫,公众号:【Java分享客栈】
* @since 2022/12/6 19:22
*/
@Slf4j
public class ExceptionTest {
public static void main(String[] args) {
try {
UserInfo userInfo = null;
System.err.println(userInfo.getName());
} catch (Exception ex) {
throw new BusinessException("捕获try的异常: " + ex.getMessage());
} finally {
System.err.println("----------finally最终执行---------");
throw new BusinessException("覆盖catch的异常");
}
}
}
复制代码
看下效果,发现catch的异常竟然被覆盖了。
虽然这种场景很特殊,但这其实就是Java在异常实现上美中不足的地方,因为异常是作为程序出错的标志绝对不应忽略,可是这种场景下异常的的确确丢失了。
接下来,我们再测试一种情况,在finally中使用return,看看会发生什么。
/**
* <p>
* 测试异常缺陷
* </p>
*
* @author 程序员济癫,公众号:【Java分享客栈】
* @since 2022/12/6 19:22
*/
@Slf4j
public class ExceptionTest {
public static void main(String[] args) {
try {
UserInfo userInfo = null;
System.err.println(userInfo.getName());
} catch (Exception ex) {
throw new BusinessException("捕获try的异常: " + ex.getMessage());
} finally {
System.err.println("----------finally最终执行---------");
return;
}
}
}
复制代码
看看效果,发现catch中捕获的异常干脆直接没了,仿佛从没来过。
最后,我们再演示一种你可能工作中干过或者见过的莫名其妙的事情。
我们修改一下这个测试方法,看代码。
/**
* <p>
* 测试异常缺陷
* </p>
*
* @author 程序员济癫,公众号:【Java分享客栈】
* @since 2022/12/6 19:22
*/
@Slf4j
public class ExceptionTest {
public static void main(String[] args) {
try {
// 调用其他类的查询方法
queryData();
} catch (Exception ex) {
// 捕获这个查询方法抛出的异常
throw new BusinessException(ex.getMessage());
}
}
private static void queryData() {
try {
// 这个方法刚好你也try..catch了,并且在finally中做了一些末尾必须执行的业务逻辑处理。
UserInfo userInfo = null;
System.err.println(userInfo.getName());
} finally {
doSomething();
}
}
private static void doSomething() {
// 业务处理过程中你同样习惯性地抛出了某个业务异常
System.err.println("--------处理末尾业务--------");
throw new BusinessException("处理末尾业务抛出逻辑异常");
}
}
复制代码
简单描述一下,你调用其他类的一个查询方法,那个方法可能习惯性的try..catch..finally了,而finally中还做了一些末尾必须要执行的操作,这个业务逻辑处理可能有几十行,你很可能又习惯性的做了一些判断以及异常的抛出。
别不相信,当一个项目进入中期甚至赶进度的时候,方法套方法,不少人已经在机械的茫然的写代码,也可能是在别人的基础上改代码,你很可能不会太仔细地一行一行去看那些代码里究竟有什么,恰巧测试的时候也没出什么大问题。
那么结果可能就是下面这样,你会发现自己一开始特意抛出的那个捕获该查询方法异常的玩意儿没一点效果,也不知道去哪儿了,怎么找也找不到,不知从哪儿冒出来下面这个莫名其妙的异常,后来想不到也就算了。
原因
这其实就是Java异常实现的一个不足,异常是程序出错的标志,怎么都不应该被忽略掉,更不用说是finally这种常用的行为,直接或间接地造成了异常的丢失。
《Thinking In Java》的作者有明确指出这个异常,认为这是相当严重的缺陷,
是一个可能造成异常完全丢失的缺陷,而且是以一种更微妙、更难以察觉的方式在进行。
而C++就处理的很好,会将这种在第一个异常被处理之前抛出第二个异常的情况视为严重的编程错误。
总结
知道了这个缺陷后,其实就很好避免了。
1、避免在finally中抛出异常;
2、避免在finally中使用return;
3、catch中一定要养成log.error记录异常日志的好习惯,因为log是一定会记录下来的,至少不会让你毫无线索。
结尾再演示下加了日志的效果,只要是catch我都加上日志,那么一定不会错过。