本文转载自微信公众号《苏三说技术》,作者因热爱坚持ing。转载本文请联系苏三硕科技公众号。前言最近,我们通过sonar静态代码检测和人工代码审查,发现了项目中的很多代码问题。除了常见的错误和安全漏洞外,还有几个方法使用错误引起了我的极大兴趣。为什么我对这些方法如此感兴趣?因为它们非常混乱,可能会让我们感到困惑。1.replace会替换所有字符吗?很多时候我们在使用字符串的时候,都想把字符串中的字符A比如:ATYSDFA*Y替换成字符B,首先想到的可能是使用replace方法。如果要用B替换所有的A,显然可以使用replaceAll方法,因为它非常直观,光看方法名就可以猜到它的用途。那么问题来了:replace方法会替换所有匹配的字符吗?jdk官方给出了答案。此方法替换每个匹配的字符串。既然replace和replaceAll都可以替换所有匹配的字符,那它们有什么区别呢?1.replace有两个重载方法。其中一个方法的参数:charoldChar和charnewChar,支持字符替换。source.replace('A','B')另一个方法的参数是:CharSequence目标和CharSequence替换,支持字符串替换。source.replace("A","B")2.replaceAll方法的参数为:Stringregex和Stringreplacement,基于正则表达式的替换。普通字符串替换:source.replaceAll("A","B")正则表达式替换(用C替换*):source.replaceAll("\\*","C")对了,用C替换*也可以使用replace方法实现:source.replace("*","C")不需要转义特殊字符。但是注意不要使用下面的写法:source.replace("\\*","C")这种写法会导致字符串不可替换。还有一个小问题,如果只想替换第一个匹配的字符串怎么办?这时候可以使用replaceFirst方法:source.replaceFirst("A","B")2.整数不能用==判断相等吗?不知道大家在项目中有没有看到。有同事用==来比较Integer类型的两个参数。反正我见过。这种用法正确吗?我的回答要看具体场景,不能说一定对错。一些状态字段,如:orderStatus有:-1(未下单)、0(已下单)、1(已付款)、2(已完成)、3(已取消)、5种状态。这时候如果用==判断是否相等:IntegerorderStatus1=newInteger(1);IntegerorderStatus2=newInteger(1);System.out.println(orderStatus1==orderStatus2);返回的结果会是真的吗?答:是假的。可能有同学会反驳,Integer范围:-128-127不是有缓存吗?为什么是假的?我们看一下Integer的构造方法:其实并没有使用缓存。那么缓存用在什么地方呢?答案在valueOf方法中:如果把上面的判断改成这样:StringorderStatus1=newString("1");StringorderStatus2=newString("1");System.out.println(Integer.valueOf(orderStatus1)==Integer.valueOf(orderStatus2));返回的结果会是真的吗?答:是的。我们需要养成良好的编码习惯,尽量少用==来判断两个Integer类型是否相等,只有在上述非常特殊的场景下才相等。而是使用equals方法判断:IntegerorderStatus1=newInteger(1);IntegerorderStatus2=newInteger(1);System.out.println(orderStatus1.equals(orderStatus2));3.在不损失精度的情况下使用BigDecimal?通常我们会把一些小数类型的字段(如:金额)定义为BigDecimal而不是Double,以避免精度损失。使用Double时,可能会出现这样的场景:doubleamount1=0.02;doubleamount2=0.03;System.out.println(amount2-amount1);正常情况下,预计amount2-amount1应该等于0.01,但执行结果为:0.009999999999999998实际结果小于预期结果。Double类型的两个参数相减会转换成二进制,因为Double的有效位数是16位,所以会出现十进制位数存储不足,这种情况下会出错。常识告诉我们使用BigDecimal来避免精度损失。但是使用BigDecimal可以避免精度损失吗?答案是不。为什么?BigDecimalamount1=newBigDecimal(0.02);BigDecimalamount2=newBigDecimal(0.03);System.out.println(amount2.subtract(amount1));本例中定义了两个BigDecimal类型的参数,通过构造函数对数据进行初始化,然后打印两个参数相减后的值。结果:0.0099999999999999984734433411404097569175064563751220703125不科学,为什么还是丢精度?jdk中BigDecimal的构造方法有这样一段描述:大意是说这个构造函数的结果可能是不可预测的,可能创建的时候看起来是0.1,但实际上是0.1000000000000000055511151231257827021181583404541015625例。可见使用BigDecimal构造函数初始化对象也会丢失精度。那么,我们如何才能不失去精度呢?BigDecimalamount1=newBigDecimal(Double.toString(0.02));BigDecimalamount2=newBigDecimal(Double.toString(0.03));System.out.println(amount2.subtract(amount1));使用双。toString方法对double类型的小数进行转换,可以保证不丢精度。其实还有更好的办法:BigDecimalamount1=BigDecimal.valueOf(0.02);BigDecimalamount2=BigDecimal.valueOf(0.03);System.out.println(amount2.subtract(amount1));使用BigDecimal.valueOf方法初始化BigDecimal类型参数,也可以保证精度不丢失。在新版阿里巴巴开发手册中,也推荐使用该方法创建BigDecimal参数。4.字符串不能用String拼接?String类型的字符串被称为不可变序列,这意味着对象的数据在定义后不能被修改。如果你想修改它,你需要创建一个新的对象。Stringa="123";Stringb="456";Stringc=a+b;System.out.println(c);在拼接大量字符串的场景下,如果将对象定义为String类型,很多无用的中间Object会浪费内存空间,效率低下。这时候,我们可以使用更高效的可变字符序列:StringBuilder和StringBuffer来定义对象。那么,StringBuilder和StringBuffer有什么区别呢?StringBuffer在每个主要方法中都添加了synchronized关键字,而StringBuilder则没有。所以,StringBuffer是线程安全的,但StringBuilder不是。其实我们很少有需要在多线程下拼接字符串的场景,所以StringBuffer其实很少用到。通常,我们建议在连接字符串时使用StringBuilder。通过它的append方法追加字符串只会生成一个对象,不会加锁,效率更高。Stringa="123";Stringb="456";StringBuilderc=newStringBuilder();c.append(a).append(b);System.out.println(c);接下来,关键问题来了:字符串拼接在使用String类型的对象时,效率一定低于StringBuilder类型的对象吧?答案是不。为什么?使用javap-cStringTest命令反编译:从图中可以看出,定义了两个String类型的参数,定义了一个StringBuilder类的参数,然后两次使用append方法追加字符串。如果代码是这样的:Stringa="123";Stringb="789";Stringc=a+b;System.out.println(c);使用javap-cStringTest命令反编译后的结果会怎样?我们会惊讶的发现还定义了两个String类型的参数,并且定义了一个StringBuilder类的参数,然后两次使用append方法追加字符串。结果和上面一样。其实从jdk5开始,java就优化了String类型字符串的+操作。将此操作编译成字节码文件后,将优化为StringBuilder的追加操作。5.isEmpty和isBlank的区别我们在对字符串进行操作的时候,经常需要判断字符串是否为空。如果不使用任何工具,我们一般会这样判断:if(null!=source&&!"".equals(source)){System.out.println("notempty");}时候,会有点麻烦,那么多jar包都封装了空字符串。目前市面上主流的工具有:spring中的StringUtils、jdbc中的StringUtils、apachecommon3中的StringUtils,但是spring中的StringUtils类只有isEmpty方法,没有isNotEmpty方法。jdbc中的StringUtils类只有isNullOrEmpty方法,没有isNotNullOrEmpty方法。所以这里强烈推荐apachecommon3中的StringUtils类,里面有很多实用的空判断方法:isEmpty、isBlank、isNotEmpty、isNotBlank等,以及其他字符串处理方法。问题来了,isEmpty和isBlank有什么区别?使用isEmpty方法判断:StringUtils.isNotEmpty(null)=trueStringUtils.isNotEmpty("")=trueStringUtils.isNotEmpty("")=falseStringUtils.isNotEmpty("bob")=falseStringUtils。isNotEmpty("bob")=false使用isBlank方法判断:StringUtils.isBlank(null)=trueStringUtils.isBlank("")=trueStringUtils.isBlank("")=trueStringUtils.isBlank("bob")=falseStringUtils.isBlank("bob")=false两种方法的主要区别在于""空字符串的情况下,isNotEmpty返回false,isBlank返回true。6、mapper查询结果是否应该判断为空?一次codereview的时候同事说这里的空判断可以去掉,记忆犹新:List
