最近我们通过sonar静态代码检测,同时配合人工代码review,发现了项目中很多代码问题。除了常规的bug和安全漏洞之外,还有几处方法用法错误,引起了我极大的兴趣。我为什么会对这几个方法这么感兴趣呢?因为它们极具迷惑性,可能会让我们傻傻分不清楚。
很多时候我们在使用字符串时,想把字符串比如:ATYSDFA*Y中的字符A
替换成字符B
,第一个想到的可能是使用replace
方法。
如果想把所有的A
都替换成B
,很显然可以用replaceAll
方法,因为非常直观,光从方法名就能猜出它的用途。
那么问题来了:replace
方法会替换所有匹配字符吗?
jdk的官方给出了答案。
该方法会替换每一个匹配的字符串。
既然replace
和replaceAll
都能替换所有匹配字符,那么他们有啥区别呢?
1 . replace
有两个重载的方法。
其中一个方法的参数:char oldChar 和 char newChar,支持字符的替换。
source.replace('A', 'B')
另一个方法的参数是:CharSequence target 和 CharSequence replacement,支持字符串的替换。
source.replace("A", "B")
2 . replaceAll
方法的参数是:String regex 和 String replacement,基于正则表达式的替换。普通字符串替换:
source.replaceAll("A", "B")
正则表达替换(将*替换成C):
source.replaceAll("\\*", "C")
顺便说一下,将*替换成C使用replace
方法也可以实现:
source.replace("*", "C")
无需对特殊字符进行转义。
不过,千万注意,切勿使用如下写法:
source.replace("\\*", "C")
这种写法会导致字符串无法替换。
还有个小问题,如果我只想替换第一个匹配的字符串该怎么办?
这时可以使用replaceFirst
方法:
source.replaceFirst("A", "B")
不知道你在项目中有没有见过,有些同事对Integer
类型的两个参数使用==
比较是否相等?
反正我见过的,那么这种用法对吗?
我的回答是看具体场景,不能说一定对,或不对。
有些状态字段,比如:orderStatus
有:-1(未下单),0(已下单),1(已支付),2(已完成),3(取消),5种状态。
这时如果用==
判断是否相等:
Integer orderStatus1 = new Integer(1);
Integer orderStatus2 = new Integer(1);
System.out.println(orderStatus1 == orderStatus2);
返回结果会是true
吗?
答案:是false
。
有些同学可能会反驳,Integer
中不是有范围是:-128
-127
的缓存吗?
为什么是false
?
先看看Integer的构造方法: 它其实并没有用到缓存。
那么缓存是在哪里用的?
答案在valueOf
方法中:
如果上面的判断改成这样:
String orderStatus1 = new String("1");
String orderStatus2 = new String("1");
System.out.println(Integer.valueOf(orderStatus1) == Integer.valueOf(orderStatus2));
返回结果会是true
吗?
答案:还真是true
。
我们要养成良好编码习惯,尽量少用==
判断两个Integer
类型数据是否相等,只有在上述非常特殊的场景下才相等。
而应该改成使用equals
方法判断:
Integer orderStatus1 = new Integer(1);
Integer orderStatus2 = new Integer(1);
System.out.println(orderStatus1.equals(orderStatus2));
通常我们会把一些小数类型的字段(比如:金额),定义成BigDecimal,而不是Double,避免丢失精度问题。
使用Double时可能会有这种场景:
double amount1 = 0.02;
double amount2 = 0.03;
System.out.println(amount2 - amount1);
正常情况下预计amount2 - amount1
应该等于0.01
但是执行结果,却为:
0.009999999999999998
实际结果小于预计结果。
Double类型的两个参数相减会转换成二进制,因为Double有效位数为16
位这就会出现存储小数位数不够的情况,这种情况下就会出现误差。
常识告诉我们使用BigDecimal能避免丢失精度。
但是使用BigDecimal能避免丢失精度吗?
答案是否定的。
为什么?
BigDecimal amount1 = new BigDecimal(0.02);
BigDecimal amount2 = new BigDecimal(0.03);
System.out.println(amount2.subtract(amount1));
这个例子中定义了两个BigDecimal类型参数,使用构造函数初始化数据,然后打印两个参数相减后的值。
结果:
0.0099999999999999984734433411404097569175064563751220703125
不科学呀,为啥还是丢失精度了?
jdk中BigDecimal的构造方法上有这样一段描述:
大致的意思是此构造函数的结果可能不可预测,可能会出现创建时为0.1,但实际是0.1000000000000000055511151231257827021181583404541015625的情况。
由此可见,使用BigDecimal构造函数初始化对象,也会丢失精度。
那么,如何才能不丢失精度呢?
BigDecimal amount1 = new BigDecimal(Double.toString(0.02));
BigDecimal amount2 = new BigDecimal(Double.toString(0.03));
System.out.println(amount2.subtract(amount1));
使用Double.toString
方法对double类型的小数进行转换,这样能保证精度不丢失。
其实,还有更好的办法:
BigDecimal amount1 = BigDecimal.valueOf(0.02);
BigDecimal amount2 = BigDecimal.valueOf(0.03);
System.out.println(amount2.subtract(amount1));
使用BigDecimal.valueOf
方法初始化BigDecimal类型参数,也能保证精度不丢失。在新版的阿里巴巴开发手册中,也推荐使用这种方式创建BigDecimal参数。
String
类型的字符串被称为不可变序列,也就是说该对象的数据被定义好后就不能修改了,如果要修改则需要创建新对象。
String a = "123";
String b = "456";
String c = a + b;
System.out.println(c);
在大量字符串拼接的场景中,如果对象被定义成String
类型,会产生很多无用的中间对象,浪费内存空间,效率低。
这时,我们可以用更高效的可变字符序列:StringBuilder
和StringBuffer
,来定义对象。
那么,StringBuilder
和StringBuffer
有啥区别?
StringBuffer对各主要方法加了synchronized
关键字,而StringBuilder没有。所以,StringBuffer是线程安全的,而StringBuilder不是。
其实,我们很少会出现需要在多线程下拼接字符串的场景,所以StringBuffer实际上用得非常少。一般情况下,拼接字符串时我们推荐使用StringBuilder
,通过它的append
方法追加字符串,它只会产生一个对象,而且没有加锁,效率较高。
String a = "123";
String b = "456";
StringBuilder c = new StringBuilder();
c.append(a).append(b);
System.out.println(c);
接下来,关键问题来了:字符串拼接时使用String类型的对象,效率一定比StringBuilder类型的对象低?
答案是否定的。
为什么?
使用javap -c StringTest
命令反编译:
从图中能看出定义了两个String类型的参数,又定义了一个StringBuilder类的参数,然后两次使用append方法追加字符串。
如果代码是这样的:
String a = "123";
String b = "789";
String c = a + b;
System.out.println(c);
使用javap -c StringTest
命令反编译的结果会怎样呢?
我们会惊讶的发现,同样定义了两个String类型的参数,又定义了一个StringBuilder类的参数,然后两次使用append方法追加字符串。跟上面的结果是一样的。
其实从jdk5开始,java就对String类型的字符串的+操作做了优化,该操作编译成字节码文件后会被优化为StringBuilder的append操作。
我们在对字符串进行操作的时候,需要经常判断该字符串是否为空。如果没有借助任何工具,我们一般是这样判断的:
if (null != source && !"".equals(source)) {
System.out.println("not empty");
}
但是如果每次都这样判断,会有些麻烦,所以很多jar包都对字符串判空做了封装。目前市面上主流的工具有:
不过spring
中的StringUtils
类只有isEmpty
方法,没有isNotEmpty
方法。
jdbc
中的StringUtils
类只有isNullOrEmpty
方法,也没有isNotNullOrEmpty
方法。
所以在这里强烈推荐一下apache common3
中的StringUtils
类,它里面包含了很多实用的判空方法:isEmpty、isBlank、isNotEmpty、isNotBlank等,还有其他字符串处理方法。
问题来了,isEmpty
和isBlank
有啥区别?
使用isEmpty
方法判断:
StringUtils.isEmpty(null) = true
StringUtils.isEmpty("") = true
StringUtils.isEmpty(" ") = false
StringUtils.isEmpty("bob") = false
StringUtils.isEmpty(" bob ") = false
使用isBlank
方法判断:
StringUtils.isBlank(null) = true
StringUtils.isBlank("") = true
StringUtils.isBlank(" ") = true
StringUtils.isBlank("bob") = false
StringUtils.isBlank(" bob ") = false
两个方法关键的区别在于这种" "
空字符串的情况,isNotEmpty
返回false,而isBlank
返回true。
有次代码review的时候,当时有个同事说这里的判空可以去掉,让我记忆犹新:
List<User> list = userMapper.query(search);
if(CollectionUtils.isNotEmpty(list)) {
List<Long> idList = list.stream().map(User::getId).collect(Collectors.toList());
}
因为按常理,一般调用方法查询出来的集合,可能为null
,需要判空的。但是,这里比较特殊,我查了一下mybatis
的源码,这个判空的代码还真的可以去掉。
怎么回事呢?
mybatis
的查询方法最终都会调到DefaultResultSetHandler类的handleResultSets方法:
该方法会返回一个multipleResults
List集合对象,在方法刚开始就new
出来了,肯定是不会为空。
所以,如果你在项目的代码中看到有人直接使用查询出的结果,不判空也不要惊讶:
List<User> list = userMapper.query(search);
List<Long> idList = list.stream().map(User::getId).collect(Collectors.toList());
因为mapper
底层已经处理过的,它不会出现空指针异常。
有次在review别人代码的时候,看到有个地方indexOf
使用了这种写法,让我印象比较深刻:
String source = "#ATYSDFA*Y";
if(source.indexOf("#") > 0) {
System.out.println("do something");
}
你们说这段代码会打印出do something
吗?
答案是否定的。
为什么呢?
jdk官方说了不存在的情况会返回-1
indexOf
方法返回的是指定元素在字符串中的位置,从0开始。而上面的例子#
在字符串的第一个位置,所以调用indexOf
方法后的值其实是0。所以,条件是false,不会打印do something
。
如果想通过indexOf
判断某个元素是否存在时,要用:
if(source.indexOf("#") > -1) {
System.out.println("do something");
}
其实,还有更优雅的contains
方法:
if(source.contains("#")) {
System.out.println("do something");
}
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/DIp9vhROicn-gwS_0CQImw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。