被JDK坑的没商量?来试试这些方法吧
前言
jdk作为我们每天必备的调用类库,里面大量提供了基础类供我们使用.可以说离开jdk,我们的java代码寸步难行,jdk带给我们的便利可谓是不胜枚举,但同时这些方法在使用起来也存在一些坑,如果不注意就很容易掉入到陷阱里面,导致程序抛出错误。jdk中的很多方法都不会做非null判断,可能设计jdk的作者默认开发者已经处理好null值了.不过这个设计可能会造成很严重的后果,实在是暗藏杀机。比如今天早上我们查了一笔订单没有退款,查了一早上最终才发现是同事写的代码的BigDecimal的subtract方法的值没有做非null判断处理导致程序抛出了空指针异常,看似简单的异常却直接无法让很多订单退款,是在是小问题造成大事故。而要修补退款这个问题,要耗费很多时间去修补,实在是让人觉得麻烦。出错的成本太高,本期我们就来看看jdk中那些坑你没商量的方法,这些方法很常见,相信你一定遇到过。
一:String.valueOf()方法的陷阱
String.valueOf()是String提供的一个类型转换的方法,我们来看一下案发现场(代码简化过后的):
Map<String, Object> userInfo = userService.getUserInfoById(userId);
Object userNameObject = userInfo.get("name");
String userName = String.valueOf(userNameObject);
// 判空
if(userName!=null&&userName.length()>0) {
String message = getMessage(userName);
smsService.send(message);
}
这段代码是简化过的,主要作用就是通过用户服务根据id获取用户信息发送短信,不过后来有客诉反应,最后的短信成了尊敬的"null"你好,xx等。开发第一时间看了代码,觉的没有问题啊,为什么短信内容会出现用户名为null呢,不是经过了非空判断的吗?后来经过定位发现了问题所在:首先用户的名字里有特殊的emoji符号,从数据库获取的时候因为符号转义的时候为null了,接下来才是重点:
这里是重点,也是最大的坑人之处,注意这里返回了一个"null"的字符串,而不是null。这两个是有很大区别的,当进行非空判断的时候,返回的是ture。
正确的处理方法:
二:Integer.parseInt()方法很矫情
事故现场:一次业务场景为拉取订单,打出订单列表记录,财务人员需要拉出对账,结果总是发现少很多数据,很奇怪的一个现象。还好财务发现了,要不然和第三方财务对账就会亏很多钱...最终发现是订单的一个字段值转Integer出错了,那个订单下的字段值是120.0通过
Integer.parseInt直接报错了,恰好开发人员认为这段开发肯定没问题,因此就没有catch异常,最后找了很久才发现(因为涉及到第三方,还让别人查了半天...). 知道真相的我们都有点汗颜,这么丁点的错误排查了很久,实在是不应该啊。
Integer.parseInt()方法用于将字符串转化为Integer类型的方法,此方法的适用方向就显得比较窄,因为是String类型的参数,没有任何限定,当在传入一些比如50.0、20L、30d、40f这类数据的情况下,
我们来看一个栗子::
会抛出异常NumberFormatException:
事实上对于这样的数据,比如小数、浮点数据、long型数据它都可以自动转换,而不是给我们抛出烦人的报错信息,如果预先知道是整数或者小数,可以用Bigdecimal转换(注意此方法不适用于double和float、Long类型的数据,比如10d,20L)
对于浮点类型、long类型的数据可以用以下方法来处理:
推荐使用hutool的NumberUtil.parseInt()方法,充分考虑到了浮点、long、小数等类型数据可能带来的解析异常的问题,hutool是一个国人开源的工具类库,这里实名推荐,容错性和处理异常能力很强
三:Bigdecimal的除法坑你没商量
众所周知,BigDecimal是处理金额最有效的数据类型,一般进行财务报表计算的时候为了防止金额出现错误,一般情况下都会采用Bigdecimal,而double、float都会存在些许的误差。你开开心心的用Bigdecimal进行了计算,而最终的结果返回却有问题,我们来看一个例子:
常见的除法用起来没有任何丝毫的问题,妥妥的.但是一旦程序中的数据出现以下情况,如果用Bigdecimal来接受前端的参数,而前端的参数是用户输入不确定的,一旦出现如下的数据,我们来看看结果:
执行结果一看,居然报错了哎:
这就是BidDecimal的坑,一旦返回的结果是无限循环小数,就会抛出ArithmeticException。因此在进行Bigdecimal除法的时候,需要进行保留小数的处理,正确的处理姿势:
四:Collections.emptyList()此list非彼list
我们先来看一个sample:
public List<String> getUserNameList(String userId) {
List<String> resultList = Collections.emptyList();
try {
resultList = userDao.getUserName(userId); } catch (Exception ex) {
logger.info(ex); } return resultList;
}
这样会抛出错误,主要问题在于Collections.emptyList()并非我们平时看到的List,此list不支持add、remove方法,否则会抛出operationNotSupportException:
结果抛出异常:
原因是:Collections.emptyList返回的并不是我们平时认识的那个list,它是一个内部常量类:
public static final List EMPTY_LIST = new EmptyList<>();
这个list并不具有add、remove元素的能力,我猜想是因为jdk设计之初的想法是将这个list作为一种只读的list,并不提供数据的写入能力,因此它仅可作为一种 空值返回,无法进行删除、添加操作。
五:list可以一边删除一边遍历吗?
我们再来看一个简单的例子:
很不幸,又双叒叕报错了:
仔细翻阅源码会发现,每次remove之前会检查元素的条数,如果发现预期的modCount和当前的modCount不一致就会抛出这个异常.modCount是list中用来记录修改次数的一个属性,当对元素进行统计的时候就会对该元素加1,而当对list边遍历边删除的话,就会造成
excepted与modCount不一致,从而抛出异常。
正确的删除姿势就是使用Iterator.remove进行遍历删除,可以规避这个问题。
六:总结
jdk的设计者有两个很大的特点:
①大多不会做非null判断
②出现错误直接throw new Exception,容错性很差,
在实际开发中,面对jdk一定要谨慎使用,jdk提供了便利的同时,也有一些我们使用上的盲区,应该养成多看源码,多注意错误性处理,防止在小问题上栽大跟头。回到最开始说的那个subtract方法的问题,因为这个问题等需要我处理完之后用户才能收到退款,这直接造成了用户体验直线下降,而部分用户还直接打电话投诉。同事一个小小的不谨慎和马虎就给公司造成了很多负面影响,技术问题虽然不大但是带来的业务影响范围很严重。所以我们必须防微杜渐,小小的问题都得细细的打磨,才能避免很多问题的产生。
- GeetTest~下一代验证(附C#案例)
- [接口测试 - http.client篇] 17 http.client之入门级接口测试框架
- 评论JS插件~多说+畅言
- jQuery HTML5 Uploader
- 1022: [SHOI2008]小约翰的游戏John【Nim博弈,新生必做的水题】
- [接口测试 - http.client篇] 16 基于http.client之POM实战一下
- 数论部分第一节:素数与素性测试【详解】
- ProtoBuf 序列化工具组件
- C++STL vector简单使用练习1
- 小解Redis 系列
- 小侃 SQL加密和性能
- 接口测试 | 25 requests + pytest测试实例
- 接口测试 | 24 requests + unittest集成你的接口测试
- 接口测试 23 requests基础入门二
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【STM32H7】第7章 RL-TCPnet V7.X网络协议栈移植(RTX5)
- TinyMCE 富文本编辑器的使用实例指导
- dotnet 在国产 UOS 系统利用 dotnet tool 工具做文件传输
- [白话解析] 深入浅出支持向量机(SVM)之核函数
- C# 线程同步之事件信号阻塞 AutoResetEvent
- [白话解析] 深入浅出最大熵模型
- [白话解析] 带你一起梳理Word2vec相关概念
- 利用SSE服务器主动向浏览器端发送消息
- [白话解析] Flink的Watermark机制
- [源码分析] 从源码入手看 Flink Watermark 之传播过程
- [白话解析]以水浒传为例学习隐马尔可夫模型
- [白话解析]用水浒传为例学习最大熵马尔科夫模型
- [白话解析] 用水浒传为例学习条件随机场
- 03.Android崩溃Crash库之ExceptionHandler分析
- 04.Android崩溃Crash库之Loop拦截崩溃和ANR