七大陷阱!99%的Java开发者都会遇到

文摘   2025-01-10 08:00   新疆  

Spring Boot 3实战案例合集》现已囊括超过80篇精选实战文章,并且此合集承诺将永久持续更新,为您带来最前沿的技术资讯与实践经验。欢迎积极订阅,享受不断升级的知识盛宴!订阅用户将特别获赠合集内所有文章的最终版MD文档(详尽学习笔记),以及完整的项目源码,助您在学习道路上畅通无阻。

【重磅发布】《Spring Boot 3实战案例锦集》PDF电子书现已出炉!

🎉🎉我们精心打造的《Spring Boot 3实战案例锦集》PDF电子书现已正式完成,目前已经有80个案例,后续还将继续更新。文末有电子书目录。

📚📚订阅获取
只需订阅我们的合集点我订阅,即可立即私信我们获取这本珍贵的电子书。轻松拥有Spring Boot 3的实战宝典!

💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。这意味着,随着技术的不断发展和Spring Boot 3的深入应用,我们的电子书也将持续更新,确保您始终掌握最前沿、最实用的技术知识。

🔥🔥精彩内容不容错过
《Spring Boot 3实战案例锦集》汇聚了众多精心挑选的实战案例,旨在帮助您快速掌握Spring Boot 3的核心技术和实战技巧。无论您是初学者还是有一定经验的开发者,都能从中受益匪浅。

💌💌如何获取
请立即订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

现在就订阅合集




环境:SpringBoot3.2.5



1. replace是否会替换所有字符?

在处理字符串时,我们经常需要替换字符串中的字符,比如在字符串 "ACDAB$%^&A*Y" 中将 A 替换为 X。首先想到的方法可能就是使用 replace 方法。

如果目的是将所有出现的 A 都替换为 X,那么使用 replaceAll 方法似乎很直观。方法名本身就清楚地表明了它的用途。

于是问题来了:replace 方法会替换所有匹配的字符吗?

JDK文档说明:

翻译:该方法将此字符串中每个与目标字面量序列相匹配的子字符串替换为指定的替换字面量序列。替换操作从字符串的开头到结尾依次进行,例如,在字符串 "aaa" 中将 "aa" 替换为 "b" 将得到 "ba" 而不是 "ab"。

那么 replace 与 replaceAll 的区别?

replace 方法有2个重载的方法:

String str = "ACDAB$%^&A*Y" ;System.err.println(str.replace('A', 'X')) ;System.err.println(str.replace("A", "X")) ;

replaceAll 方法签名:

public String replaceAll(String regex, String replacement)

可以通过正则表达式的方式进行替换。如下示例:

String str = "ACDAB$%^&A*Y" ;// 简单字符串替换System.err.println(str.replaceAll("A", "X")) ;// 正则替换,替换 '*' 字符,需要转义System.err.println(str.replaceAll("\\*", "XO")) ;

如果仅仅是将 '*' 进行替换,那么使用replace更简单

System.err.println(str.replace("*", "XO")) ;

以上都是替换整个字符串中匹配的,如果你只希望替换第一个出现的,那么可以使用如下方法:

System.err.println(str.replaceFirst("A", "-")) ;

第一个参数接受的是正则表达式。

2. Integer类型不要用 "==" 判断

这不是绝对的,需要看情况,你比较的数值大小了。如下示例:

Integer a = 1 ;Integer b = 1 ;System.err.printf("a == b ? %s%n", a == b) ;
a = 128 ;b = 128 ;System.err.printf("a == b ? %s%n", a == b) ;
a = -128 ;b = -128 ;System.err.printf("a == b ? %s%n", a == b) ;
a = -129 ;b = -129 ;System.err.printf("a == b ? %s%n", a == b) ;

输出结果

a == b ? truea == b ? falsea == b ? truea == b ? false

为什么这样?通过javap反编译后

当我们将值赋给Integer类型变量时调用的是Integer#valueOf静态方法,该方法签名如下:

public static Integer valueOf(int i) {  if (i >= IntegerCache.low && i <= IntegerCache.high)    return IntegerCache.cache[i + (-IntegerCache.low)] ;  return new Integer(i);}

这里的low与high取值如下:

默认[low, high] = [-128, 127],也就是说默认Integer缓存了这个范围的数字,只要取值在这个范围,那么都将返回缓存的数据。这也就是上面输出结果的原因了。

通过上面的源码我们也看到了,我们是可以通过jvm参数来改变这默认缓存大小的,运行程序时添加如下的jvm参数:

-Djava.lang.Integer.IntegerCache.high=128

这样设置后,我们在运行上面程序,128的比较将打印true 。

3. 使用BigDecimal能否避免精度损失?

通常,对于涉及小数(例如金额)的字段,我们会将它们定义为BigDecimal而不是Double,以避免精度损失。考虑以下使用Double的场景:

double a = 0.02;double b = 0.03;System.out.println(a - b);

最终结果我们期望的是0.01,但实际是:

0.009999999999999998

这是因为两个double值的减法运算会被转换为二进制形式,而double的有效数字精度限制为16位,这可能导致小数位的存储不足,从而产生误差。

那么使用BigDecimal是否能解决呢?

BigDecimal a1 = new BigDecimal(0.02) ;BigDecimal b1 = new BigDecimal(0.03) ;System.err.println(b1.subtract(a1)) ;

执行结果

0.0099999999999999984734433411404097569175064563751220703125

为什么?我们先看看BigDecimal的构造函数说明:

我们看上面的第一点即可:

翻译:这个构造函数的结果可能会有些不可预测。人们可能会认为,在Java中写new BigDecimal(0.1)会创建一个完全等于0.1的BigDecimal(未缩放值为1,精度为1),但实际上它等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1在双精度浮点数(或者任何有限长度的二进制小数)中无法被精确表示。因此,尽管表面上看起来如此,但传递给构造函数的值并不完全等于0.1。

这也说明了,我们直接通过构造函数传入的double类型进行计算是有风险的。

接着我们看第二点:

翻译:String 构造函数是完全可预测的:写 new BigDecimal("0.1") 会创建一个完全等于 0.1 的 BigDecimal,正如人们所期望的那样。因此,通常建议优先使用 String 构造函数而不是这个(指直接使用 double 值的)构造函数。

我们将上面的代码改为如下:

BigDecimal aa = new BigDecimal("0.02") ;BigDecimal bb = new BigDecimal("0.03") ;System.err.println(bb.subtract(aa)) ;// 0.01

输出正确

我们还可以通过如下的方式:

aa = BigDecimal.valueOf(0.02) ;bb = BigDecimal.valueOf(0.03) ;System.err.println(bb.subtract(aa)) ;// 0.01

此种方式是不是更加方便。其BigDecimal#valueOf内如如下:

关于BigDecimal更多内容请查看下面文章:

不想被坑?快来了解BigDecimal的陷阱

4. 是否真的不能使用 "+" 拼接字符串?

字符串值被视为不可变的序列。这意味着一旦定义了字符串对象,其数据就不能被修改。如果需要进行修改,则会创建一个新的对象。如下示例:

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

在涉及大量字符串拼接的场景中,使用String对象会创建许多不必要的中间对象。这不仅浪费内存空间,还会降低效率。

在这种情况下,我们可以使用更高效的可变字符序列,如StringBuilder或StringBuffer来定义对象。

那么,StringBuilder和StringBuffer有什么区别呢?

主要区别在于,StringBuffer在其主要方法上添加了synchronized关键字,而StringBuilder则没有。因此:

  • StringBuffer是线程安全的。

  • StringBuilder不是线程安全的。


在大多数情况下,建议使用StringBuilder进行字符串拼接,触发你需要在多线程环境下进行字符串的操作。

StringBuilder中的append方法可以在不创建中间对象的情况下拼接字符串,因此它更高效,而且它不是同步的。

String a = "123";String b = "456";StringBuilder c = new StringBuilder();c.append(a).append(b);System.out.println(c);

那么使用String进行字符串拼接是否总是比使用StringBuilder效率低?

首先,我们通过javap反编译上面使用StringBuilder的代码:

通过反编译,定义了2个String变量,创建一个StringBuilder对象,最后使用了2次append方法。

最后,我们再反编译使用 "+" 操作符的方式:

对比下,基本一样啊。

注意:从JDK 5开始,Java对String类型的字符串的+操作进行了优化。这个操作在编译成字节码文件时,+操作会被转换成StringBuilder的append方法调用,以提高效率。

5. isEmpty & isBlank区别

当我们执行字符串操作时,经常需要检查字符串是否为空。如果我们不使用任何工具,通常会像这样进行检查:

public static void check(String source) {  if (null != source && !"".equals(source)) {    System.out.println("not empty");  }}

如果我们每次都需要进行这样的检查,那可能会非常繁琐。推荐使用Apache Commons Lang 3中的StringUtils类,它包含了许多有用的空值检查方法:isEmpty、isBlank、isNotEmpty、isNotBlank,以及其他字符串处理方法。

接下来, 我们来看看isEmpty与isBlank的区别。

StringUtils.isEmpty(null) ;StringUtils.isEmpty("") ;StringUtils.isEmpty(" ") ;StringUtils.isEmpty("bob") ;StringUtils.isEmpty("  bob  ") ;

使用isBlank

StringUtils.isBlank(null)      = trueStringUtils.isBlank("")        = trueStringUtils.isBlank(" ")       = trueStringUtils.isBlank("bob")     = falseStringUtils.isBlank("  bob  ") = false

这两种方法的关键区别在于,对于空字符串 " " 的情况,isEmpty 返回 false,而 isBlank 返回 true。

6. Mapper返回的集合List是否进行Null检查?

如下代码,是否需要进行null检查?

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

注:CollectionUtils使用的是commons-collections4包。内部如下调用

public static boolean isEmpty(final Collection<?> coll) {  return coll == null || coll.isEmpty();}

现在我们要确定的是如果基于MyBatis查询返回的集合是否需要进行null检查呢?

查看MyBatis源码,DefaultResultSetHandler#handleResultSets方法。

collapseSingleResultList方法

private List<Object> collapseSingleResultList(List<Object> multipleResults) {  return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;}

通过查看源码得知,我们没有必要进行null的检查,得到结果后可以直接进行使用。

7. 正确使用indexOf方法

首先,我们先来看看下面的代码:

String source = "#ABBXXXOOO*pack";if (source.indexOf("#") > 0) {  System.out.println("success") ;  // TODO}

此代码并不会输出任何东西。indexOf如果不存在,那么返回 -1。该方法的说明:

指定子字符串第一次出现的索引,如果没有这样的出现,则返回-1。

indexOf方法返回指定元素在字符串中的位置,从0开始计数。在上面的例子中,#位于字符串的第一个位置,所以indexOf方法返回的值实际上是0。

所以,这里我们应该这样判断

if (source.indexOf("#") > -1) {  // ...}

但是,我觉得下面的方法更好:

if (source.contains("#")) {  System.err.println("contains success") ;}

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

Spring Cloud Gateway 网关非常实用的8个开发技巧,你知道吗?太实用了

SpringBoot读取配置文件信息8种方式,你会哪几种?

强大!Spring Boot 一个注解搞定接口限流

高级开发!Spring Boot 自定义SQL日志记录(包括, 参数,耗时),支持MyBatis,JPA等

项目亮点!Spring Boot 多线程事务一致性方案,支持JDBC,MyBatis,JPA

不写一行代码通过UI界面配置HTTP接口

新选择!基于Spring Boot监听MySQL日志Binlog实现数据实时同步

Jackson在Spring Boot高级应用技巧【Long精度丢失, @JsonValue, 数据脱敏】

在 Spring Boot 中加载属性文件的7种方法

优雅!SpringBoot详细记录SQL执行耗时情况

性能提升!@Async与CompletableFuture优雅应用

强大!SpringBoot通过这3个注解监测Controller接口

自己动手实现精简版SpringBoot原来如此简单

Spring全家桶实战案例源码
spring, springboot, springcloud 案例开发详解
 最新文章