Java中这7个方法,一不小心就用错了!

最近我们通过sonar静态代码检测,同时配合人工代码review,发现了项目中很多代码问题。除了常规的bug和安全漏洞之外,还有几处方法用法错误,引起了我极大的兴趣。我为什么会对这几个方法这么感兴趣呢?因为它们极具迷惑性,可能会让我们傻傻分不清楚。

1. replace会替换所有字符?

很多时候我们在使用字符串时,想把字符串比如:ATYSDFA*Y中的字符A替换成字符B,第一个想到的可能是使用replace方法。

如果想把所有的A都替换成B,很显然可以用replaceAll方法,因为非常直观,光从方法名就能猜出它的用途。

那么问题来了:replace方法会替换所有匹配字符吗?

jdk的官方给出了答案。

该方法会替换每一个匹配的字符串。

既然replacereplaceAll都能替换所有匹配字符,那么他们有啥区别呢?

  1. replace有两个重载的方法。

其中一个方法的参数:char oldChar 和 char newChar,支持字符的替换。

source.replace('A', 'B')

另一个方法的参数是:CharSequence target 和 CharSequence replacement,支持字符串的替换。

source.replace("A", "B")
  1. 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")

2. Integer不能用==判断相等?

不知道你在项目中有没有见过,有些同事对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));

3. 使用BigDecimal就不丢失精度?

通常我们会把一些小数类型的字段(比如:金额),定义成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参数。

4. 字符串拼接不能用String?

String类型的字符串被称为不可变序列,也就是说该对象的数据被定义好后就不能修改了,如果要修改则需要创建新对象。

String a = "123";
String b = "456";
String c = a + b;
System.out.println(c);

在大量字符串拼接的场景中,如果对象被定义成String类型,会产生很多无用的中间对象,浪费内存空间,效率低。

这时,我们可以用更高效的可变字符序列:StringBuilderStringBuffer,来定义对象。

那么,StringBuilderStringBuffer有啥区别?

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操作。

5. isEmpty和isBlank的区别

我们在对字符串进行操作的时候,需要经常判断该字符串是否为空。如果没有借助任何工具,我们一般是这样判断的:

if (null != source && !"".equals(source)) {System.out.println("not empty");
}

但是如果每次都这样判断,会有些麻烦,所以很多jar包都对字符串判空做了封装。目前市面上主流的工具有:

  • spring中的StringUtils

  • jdbc中的StringUtils

  • apache common3中的StringUtils

不过spring中的StringUtils类只有isEmpty方法,没有isNotEmpty方法。

jdbc中的StringUtils类只有isNullOrEmpty方法,也没有isNotNullOrEmpty方法。

所以在这里强烈推荐一下apache common3中的StringUtils类,它里面包含了很多实用的判空方法:isEmpty、isBlank、isNotEmpty、isNotBlank等,还有其他字符串处理方法。

问题来了,isEmptyisBlank有啥区别?

使用isEmpty方法判断:

 StringUtils.isEmpty(null)      = trueStringUtils.isEmpty("")        = trueStringUtils.isEmpty(" ")       = falseStringUtils.isEmpty("bob")     = falseStringUtils.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。

6. mapper查询结果要判空?

有次代码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方法:

该方法会返回一个multipleResultsList集合对象,在方法刚开始就new出来了,肯定是不会为空。

所以,如果你在项目的代码中看到有人直接使用查询出的结果,不判空也不要惊讶:

List<User> list = userMapper.query(search);
List<Long> idList = list.stream().map(User::getId).collect(Collectors.toList());

因为mapper底层已经处理过的,它不会出现空指针异常。

7. indexOf方法的正确用法

有次在review别人代码的时候,看到有个地方indexOf使用了这种写法,让我印象比较深刻:

String source = "#ATYSDFA*Y";
if(source.indexOf("#") > 0) {System.out.println("do something");
}

你们说这段代码会打印出do something吗?

答案是否定的。

为什么呢?

jdk官方说了不存在的情况会返回-1indexOf方法返回的是指定元素在字符串中的位置,从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");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544820.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 标志一个方法为过时方法

使用 Deprecated 来标记方法 Deprecated//用来判断ip是否合法public boolean checkIp(String tempIp) {String regex "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)){3}"; // String regex2 "([1-9]|[1-9]\\\\d…

STL之顺序容器

顺序容器&#xff1a; vector&#xff1a;数组 list:链表 deque&#xff1a;双端数组 顺序容器适配器&#xff1a; stack&#xff1a;堆栈 queue&#xff1a;队列 priority_queue&#xff1a;优先级队列 deque是一个动态数组  deque与vector非常类似&#xff1b;  deque可以…

Java StringBuilder reverse()方法与示例

StringBuilder类reverse()方法 (StringBuilder Class reverse() method) reverse() method is available in java.lang package. reverse()方法在java.lang包中可用。 reverse() method is used to reverse this character sequence by the reverse of the sequence. reverse()…

这样设置,让你的 IDEA 好看到爆炸

今天这期我们来分享几个美化 IDEA 设置技巧&#xff0c;让你的 IDEA 与众不同。首先我们来看下 IDEA 默认设置&#xff0c;虽然不丑&#xff0c;但就是太单调&#xff0c;千篇一律。默认主题接着&#xff0c;我们来看下美化以后的界面&#xff0c;总体看起来是不是比默认好看了…

IMP-00002: 无法打开 D:\orcldat\test_20111024.dmp 进行读取,rman备份

文章转自&#xff1a;http://blog.csdn.net/wanglilin/article/details/6900633 首先&#xff0c;我的路径写错了&#xff0c;文件夹是orcldata我掉了个a。 其次&#xff0c;命令后添加 fully。 dos下随便哪个目录> [sql] view plaincopyprint? IMP username/pwddbname BU…

observable_Java Observable setChanged()方法与示例

observable可观察的类setChanged()方法 (Observable Class setChanged() method) setChanged() method is available in java.util package. setChanged()方法在java.util包中可用。 setChanged() method is used to set this Observable object status as changed. setChanged…

RabbitMQ 集群

2019独角兽企业重金招聘Python工程师标准>>> Clustering Guide A RabbitMQ broker is a logical grouping of one or several Erlang nodes, each running the RabbitMQ applicationand sharing users, virtual hosts, queues, exchanges, etc. Sometimes we refer …

使用uuid作为数据库主键,被技术总监怼了!

一、前言在日常开发中&#xff0c;数据库中主键id的生成方案&#xff0c;主要有三种数据库自增ID采用随机数生成不重复的ID采用jdk提供的uuid对于这三种方案&#xff0c;我发现在数据量少的情况下&#xff0c;没有特别的差异&#xff0c;但是当单表的数据量达到百万级以上时候&…

Android设置透明、半透明等效果

首先说明一点&#xff0c;关于透明的 Android 控件 background 问题&#xff0c;从转载来的文章看到最主要的一句有用的代码是&#xff1a; v.getBackground().setAlpha(100);//0~255透明度值 这里的 Alpha 值&#xff0c;实际上是 0-1 的取值范围。 以下内容转自&#xff…

Java Long类shortValue()方法与示例

长类shortValue()方法 (Long class shortValue() method) shortValue() method is available in java.lang package. shortValue()方法在java.lang包中可用。 shortValue() method is used to return the value denoted by this Long object converted to type short (by casti…

ThreadLocal不好用?那是你没用对!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 中&#xff0c;如果要问哪个类使用简单&#xff0c;但用好最不简单&#xff1f;我想你的脑海中一定会浮现出一次词—…

记一则js替换字符串的问题

2019独角兽企业重金招聘Python工程师标准>>> 软件的一处功能用到EasyUI的表单提交&#xff0c;返回一串字符串&#xff0c;这串字符串里有一段HTML代码&#xff0c;正常的情况下这段HTML代码里的双引号“ 是用 \ 转义过的。在IE中没问题&#xff0c;但是在Firefox和…

『图解Java并发』面试必问的CAS原理你会了吗?

在并发编程中我们都知道i操作是非线程安全的&#xff0c;这是因为 i操作不是原子操作。如何保证原子性呢&#xff1f;常用的方法就是加锁。在Java语言中可以使用 Synchronized和CAS实现加锁效果。Synchronized是悲观锁&#xff0c;线程开始执行第一步就是获取锁&#xff0c;一旦…

美易官方:“圣诞老人行情”美股能否延续近期涨势?

圣诞老人行情”有望上演&#xff0c;美股能否延续近期涨势&#xff1f; 随着圣诞节的临近&#xff0c;市场开始期待所谓的“圣诞老人行情”能够上演。在过去的几年里&#xff0c;这个时期往往会出现一波上涨行情&#xff0c;给投资者带来一些安慰和喜悦。然而&#xff0c;今年的…

Linux高并发应用类型对系统内核的优化

Linux操作系统内核参数优化net.ipv4.tcp_max_tw_buckets 6000 net.ipv4.ip_local_port_range 1024 65000 net.ipv4.tcp_tw_recycle 1 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_syncookies 1 net.core.somaxconn 262144 net.core.netdev_max_backlog 262144 net.ipv4.tcp_m…

Java LinkedHashMap forEach()方法与示例

LinkedHashMap类的forEach()方法 (LinkedHashMap Class forEach() method) forEach() method is available in java.util package. java.util包中提供了forEach()方法 。 forEach() method is used to perform the specified action for every entry (key-value) pairs in the …

SimpleDateFormat线程不安全的5种解决方案!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;1.什么是线程不安全&#xff1f;线程不安全也叫非线程安全&#xff0c;是指多线程执行中&#xff0c;程序的执行结果和预期的…

Java LineNumberReader mark()方法与示例

LineNumberReader类mark()方法 (LineNumberReader Class mark() method) mark() method is available in java.io package. mark()方法在java.io包中可用。 mark() method is used to set the current position in this LineNumberReader stream and whenever we call to reset…

mac地址漂移flapping的前因后果

一、什么是mac地址flapping?mac地址漂移是指&#xff1a;在同一个vlan内&#xff0c;mac地址表项的出接口出现变更。如图&#xff1a;二、产生的原因1、因为环路或VRRP切换&#xff0c;导致的MAC地址漂移告警。&#xff08;不予关注&#xff09;2、因为无线用户漫游&#xff0…

时间转换竟多出1年!Java开发中的20个坑你遇到过几个?

前言最近看了极客时间的《Java业务开发常见错误100例》&#xff0c;再结合平时踩的一些代码坑&#xff0c;写写总结&#xff0c;希望对大家有帮助&#xff0c;感谢阅读~1. 六类典型空指针问题包装类型的空指针问题级联调用的空指针问题Equals方法左边的空指针问题ConcurrentHas…