Leader:这样的 Bug 你也写的出来???

楼下小黑哥 小黑十一点半

Hello~各位读者新年好!不知道大家春节假期是否已延长,小黑哥刚接到通知,假期延长到 2 月 2 号,另外回去之后需要在家办公,自行隔离两周。还没试过在家办公,小黑哥就怕到时候生物钟还没调整过来,一觉睡醒已经是下午了。。。

前言

春节假期,还躺在床上小黑哥,收到对账系统的一条预警短信,提示当前系统资金核对存在问题。关于资金的问题,都是大问题,小黑哥连忙拔出电脑,连上 ***,登录生产环境的查看相关日志。

通过日志,很快小黑哥定位到相关代码。

有的同学可能一下子就能看出这里的问题,Long 对象采用!=进行比较,这真是一个低级Bug。幸好 Leader 还不知道,赶紧悄悄修复一下。


现在回想小黑哥当初写这段代码的时候,误以为两个Long对象比较将会进行自动拆箱,转变为两个基本数值类型比较。

下面开始复习一下Java自动装箱与拆箱机制。

自动装箱与拆箱机制

自动装箱(Autoboxing),是JDK5新增的一种语法糖,将会在代码编译时自动将原始类型转换为其对应的对象包装器类。例如将int转换为Integer,double 转换为Double。如果转换结果相反,我们就将其称为拆箱。

下面是一个自动装箱的例子:


上面代码li.add(i)就发生自动装箱,将基本数据类型long转换为其包装类Long。

查看这段代码对应的字节码。

图上黄线标注的字节码对应的代码li.add(i)。从这我们可以看到 long 类型的自动装箱实际上调用Long#valueOf方法。所以编译器运行时将之前的代码转换为下面的代码

接下来我们来看一个自动拆箱的例子:

由于Long包装类对象不能用于求模(%)以及 +=,所以这段代码将会发生自动拆箱,将Long转化为long类型。相应的这里我们也看下其编译之后的字节码。

这里的字节码比之前的复杂很多,这里主要关注黄线部分。可以看到这里调用 Long#longValue将Long对象转为long类型。所以自动拆箱这个例子,最后编译器生成字节码等同于以下代码:

Java规定的 8 种数据类型都有其对应包装类,这些都可以进行相应的自动装箱和拆箱。

这里小结一下:

  1. 自动装箱机制是通过调用包装器类valueOf实现
  2. 自动拆箱机制通过调用包装器类的相应的**Value,如longValue, intValue实现

    Cache 陷阱

自动装箱和拆箱概念说起其实挺简单的,但是如果使用不当可能就会踩坑。

我们来看一段代码:

如果你对上面的结果的不是很清楚,恭喜你,暖男小黑哥帮你排雷了。

上面输出结果为:

1true2false

这里输出结果之所以为这样,主要与LongCache有关。自动装箱进制将会调用Long#valueOf,其源码如下:

只要数值范围位于[-128,127]之间,valueOf就会返回LongCache.cache这个数组中的值。所以aLong/bLon其实是同一个对象,cLong/dLong是两个不用对象的。

Long#valueOf这个方法通过Cache减少创建对象的数量,提高相应的空间以及时间性能。

至于为什么缓存[-128,127]之间的数字,而没有缓存更多的值,甚至缓存所有的值?

这是因为Long范围为[-2^63,2^63],总共 2^64 ,这么多对象实例,显然不宜全部缓存。至于 [-128,127] 之间数字JDK设计者认为这些数字使用频率高,个人认为这是一个经验值。

另外这个cache可能会导致另外一个问题:锁共用。

上面代码看起来 A 类使用along这个对象锁,而B类使用blong这个对象锁,看起来两个风马牛不相及,但是实际上两个对象由于Cache机制,导致其实际上使用同一个对象,A与B共用同一把锁。

除了Long#valueOf方法中存在Cache以外,Byte,Short,Integer,Character也存在相应的Cache值。其中Integer对象可以通过-XX:AutoBoxCacheMax=<size>改变Cache初始范围。

技术总结

通过自动装箱与拆箱机制,大大减少了数据类型转化的代码,但是同时带来一些隐藏的问题。这里我们需要记住:

  1. 所有对象之间不能使用==比较,需要使用equals代替,推荐使用 JDK7提供的Objects#equals方法,该方法可以有效避免比较过程空指针问题
  2. 基本数据类型的包装类不适合当做锁对象
    各位读者朋友们,请牢记这两个总结,不要踩中这两个坑哦。另外推荐大家安装一下FindBugs这个插件,通过这个插件扫描代码,可以找出代码中的相关缺陷。上面两个问题,通过FindBugs都能扫描出来,简直神器。

对象比较问题:

锁问题

帮助文档

  1. https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
  2. 极客时间-『Java并发编程实战』专栏
©著作权归作者所有:来自51CTO博客作者mb5ff59251db416的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 使用类型注解让 Python 代码更易读
  2. 200 行代码实现一个滑动验证码
  3. 浏览器中的JavaScript:文档对象模型与 DOM 操作[每日前端夜话0x5F
  4. 对你没有看错!不到 10 行代码完成抖音热门视频的爬取!
  5. 作为一个Python爱好者,如何写出高可读性的代码?
  6. 嫌pandas慢又不想改代码怎么办?来试试Modin
  7. Javascript的对象拷贝[每日前端夜话0x53]
  8. 使用ESLint + Prettier简化代码 Review 过程[每日前端夜话0x4E]
  9. 11个 Javascript 小技巧帮你提升代码质量,干货收藏!

随机推荐

  1. 漫画 | Bug是如何产生的?
  2. 015. 三数之和 | Leetcode题解
  3. 数据库密码配置项都不加密?心也太大了!
  4. 漫画 | 前端发展史的江湖恩怨情仇
  5. 栈的应用(括号匹配问题)
  6. 如何使你的开源项目成功[每日前端夜话0xD
  7. Spring AOP
  8. FAlinux03基础
  9. 在 JavaScript 中轻松处理 this [每日前
  10. Spring事务