99%的 Java 程序员极易陷入的 5 处陷阱!

大家好,我是码牛,又跟大家见面啦!

身为 Java 程序员的您,不知是否曾踏入过某些基础知识的误区。时而,某个漏洞,您耗费大量时间查找,最终惊觉竟是一个极为低级的差错。时而,某些代码,对于这一批数据其功能正常,然而更换一批数据便出现了异常状况。时而,您或许会望着某行代码瞠目结舌,心中思忖:这行代码缘何出错?今日与诸位一同探讨 99%的 Java 程序员已然踏入,亦或即将面临的 5 个误区。

1. 用“==”号比较的误区

不知您在项目中是否目睹过,有些同仁针对 Integer 类型的两个参数运用“==”号来比较是否相等?反正于我而言是见过的,那么此种用法正确与否?我的答复是视具体情形而定,不可断言一定正确,亦或错误。

某些状态字段,例如:orderStatus 存有:-1(未下单)、0(已下单)、1(已支付)、2(已完成)、3(取消),这 5 种状态。此时倘若使用“==”判断是否相等:

返回结果会是 true 吗?答案:为 false 。

部分同学或许会予以反驳,在 Integer 当中不是存在范围为:-128 至 127 的缓存吗?缘何会是 false ?首先来审视一下 Integer 的构造方法:

它实际上并未运用到缓存。那么缓存是在何处被使用的?答案存在于 valueOf 方法之中:

如果上面的判断改成这样:

返回结果会是 true 吗?答案:确为 true 。

我们应当养成优良的编码习惯,尽可能减少使用“==”来判断两个 Integer 类型数据是否相等,唯有在上述极为特殊的场景下二者才相等。而应当变更为使用 equals 方法进行判断:

运行结果为true。

2. Objects.equals的坑

假设当下存在这样一个需求:对当前登录的用户予以判定,如果其为我们所指定的系统管理员,那么发送一封邮件。

系统管理员不存在特殊的字段标识,其用户 id 等于 888,在开发、测试以及生产环境中该数值均是相同的。此需求着实极易实现:

从当前登录用户的上下文当中获取用户信息,并进行判断。倘若用户信息为空,那么直接返回。若获取到的用户信息不为空,接下来则判断用户 id 是否等同于 888 。若等于 888 ,则发送邮件;若不等于 888 ,则不进行任何操作。

当我们使用 id 为 888 的系统管理员账号登录之后,进行了相关操作,满心期待地准备接收邮件之时,却发现一无所获。其后,发现 UserInfo 类是如此定义的:

此时,部分伙伴或许会言:未察觉出有何问题呀。然而我所要陈述的是,此段代码的确存在问题。

究竟是何问题呢?接下来我们着重审视它的 equals 方法:

equals 方法的判断逻辑如下:该方法首先判断对象 a 和 b 的引用是否相等,若相等则直接返回 true 。倘若引用不相等,则判别 a 是否为空,若 a 为空则返回 false 。

若 a 不为空,则调用对象的 equals 方法进一步判定值是否相等。这便要从 Integer 的 equals 方法谈起了。其 equals 方法的具体代码如下:

首先判断参数 obj 是否为 Integer 类型,若否,则直接返回 false 。若为 Integer 类型,再进一步判断 int 值是否相等。

而在上述这个例子当中,b 是 long 类型,所以 Integer 的 equals 方法直接返回了 false 。也就是说,倘若调用了 Integer 的 equals 方法,必然要求传入的参数也是 Integer 类型,否则该方法将会直接返回 false 。

除此之外,还有 Byte 、Short 、Double 、Float 、Boolean 和 Character 也具备类似的 equals 方法判断逻辑。

常见的陷阱有:Long 类型与 Integer 类型的比较,例如:用户 id 的场景。Byte 类型与 Integer 类型的比较,例如:状态判断的场景。Double 类型与 Integer 类型的比较,例如:金额为 0 的判断场景。

3. BigDecimal的坑

通常我们会把一些小数类型的字段(比如:金额),定义成BigDecimal,而不是Double,避免丢失精度问题。

使用Double时可能会有这种场景:

正常情况下预计amount2 - amount1应该等于0.01

但是执行结果,却为:

实际结果小于预计结果。Double 类型的两个参数相减会被转换为二进制,由于 Double 的有效位数为 16 位,这便会产生存储小数位数不足的状况,在此种情形下就会出现误差。

常识昭示我们,运用 BigDecimal 能够规避精度的丢失。然而,使用 BigDecimal 真能避免丢失精度吗?答案是否定的。缘何如此?

这个例子中定义了两个BigDecimal类型参数,使用构造函数初始化数据,然后打印两个参数相减后的值。

结果:

不科学呀,为啥还是丢失精度了?

Jdk中BigDecimal的构造方法上有这样一段描述:

大致的含义为,此构造函数的结果或许难以预测,可能会呈现出创建时为 0.1 ,但实际却是 0.1000000000000000055511151231257827021181583404541015625 的情形。

由此观之,运用 BigDecimal 构造函数来初始化对象,同样会丧失精度。那么,究竟怎样才能不丢失精度呢?

我们可以使用Double.toString方法,对double类型的小数进行转换,这样能保证精度不丢失。

其实,还有更好的办法:

使用BigDecimal.valueOf方法初始化BigDecimal类型参数,也能保证精度不丢失。在新版的阿里巴巴开发手册中,也推荐使用这种方式创建BigDecimal参数。

4. Java8 filter的坑

对于Java8中的Stream用法,大家肯定再熟悉不过了。

我们通过对集合的Stream操作,可以实现:遍历集合、过滤数据、排序、判断、转换集合等等,N多功能。

这里重点说说数据的过滤。

在没有Java8之前,我们过滤数据一般是这样做的:

通常需要另一个集合辅助完成这个功能。

但如果使用Java8的filter功能,代码会变得简洁很多,例如:

代码简化了很多,完美。

但如果你对过滤后的数据,做修改了:

你当时可能只是想修改过滤后的数据,但实际上,你会把元素数据一同修改了。

意不意外,惊不惊喜?

其根本原因是:过滤后的集合中,保存的是对象的引用,该引用只有一份数据。

也就是说,只要有一个地方,把该引用对象的成员变量的值,做修改了,其他地方也会同步修改。

如下图所示:

5. 自动拆箱的坑

Java5之后,提供了自动装箱和自动拆箱的功能。

自动装箱是指:JDK会把基本类型,自动变成包装类型。

比如:

等价于:

而自动拆箱是指:JDK会把包装类型,自动转换成基本类型。

例如:

等价于:

但实际工作中,我们在使用自动拆箱时,往往忘记了判空,导致出现NullPointerException异常。

5.1 运算

很多时候,我们需要对传入的数据进行计算,例如:

如果传入了null值:

则会直接报错。

5.2 传参

有时候,我们定义的某个方法是基本类型,但实际上传入了包装类,比如:

如果出现add方法报NullPointerException异常,你可能会懵逼,int类型怎么会出现空指针异常呢?

其实,这个问题出在:Integer类型的参数,其实际传入值为null,JDK字段拆箱,调用了它的intValue方法导致的问题。

说实话,不得不承认这里所涵盖的内容本质上皆属于相当基础的范畴。然而,往往越是基础的事物,越容易使人麻痹大意,从而犯下疏忽性的错误,导致如同“大意失荆州”般的后果,并且陷入各种意想不到的陷阱之中。

从过往的经验以及众多案例分析来看,基础的知识和操作常常被人们轻视,认为其简单易懂,无需过多关注。但正是这种轻视的态度,使得人们在处理基础内容时,缺乏应有的谨慎和细致,进而增加了踩坑的可能性。

最后,让我们进行一番统计。在此,想询问一下,在面对这些看似简单却暗藏玄机的基础坑时,一个都未曾踩过的同学,可否麻烦您举个手,让大家能够以您为榜样,汲取经验和力量!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值