当前位置: 首页 > ai >正文

JAVA自动装箱拆箱


引言

Java 中的**装箱(Boxing)拆箱(Unboxing)**是自动类型转换的机制,用于在基本数据类型(如 intlong 等)和其对应的包装类(如 IntegerLong 等)之间进行转换。这种机制简化了代码的编写,但也可能引发一些性能问题或意外行为。

通过以下案例和字节码分析,我们将深入探讨装箱和拆箱的原理及其在实际开发中的应用。


案例代码与输出

public class Test {public static void main(String[] args) {Integer a = 1;          // 自动装箱Integer b = 2;          // 自动装箱Integer c = 3;          // 自动装箱Integer d = 3;          // 自动装箱Integer e = 321;        // 自动装箱Integer f = 321;        // 自动装箱Long g = 3L;            // 自动装箱System.out.println(c == d);           // trueSystem.out.println(e == f);           // falseSystem.out.println(c == (a + b));     // trueSystem.out.println(c.equals(a + b));  // trueSystem.out.println(g == (a + b));     // trueSystem.out.println(g.equals(a + b));  // false}
}
输出结果:
true
false
true
true
true
false

逐行解析与字节码分析

1. c == d 输出 true
  • 原因cd 都是 Integer 类型,值为 3
  • 装箱过程Integer c = 3 实际上被编译器翻译为 Integer c = Integer.valueOf(3)
  • 缓存机制Integer.valueOf 方法会对 -128 ~ 127 范围内的整数使用缓存池。因此,cd 指向同一个缓存对象。
  • 字节码分析
     ICONST_3INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;ASTORE 3ICONST_3INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;ASTORE 4
    
    • ASTORE 3ASTORE 4 分别存储 cd
    • 因为 3 在缓存范围内,cd 引用的是同一个对象,因此 c == d 返回 true

2. e == f 输出 false
  • 原因ef 的值为 321,超出了 Integer 缓存范围(默认 -128 ~ 127),因此每次调用 Integer.valueOf(321) 都会创建新的对象。
  • 字节码分析
     SIPUSH 321INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;ASTORE 5SIPUSH 321INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;ASTORE 6
    
    • SIPUSH 将常量 321 压入栈顶。
    • INVOKESTATIC 调用 Integer.valueOf 方法。
    • 因为 321 不在缓存范围内,ef 是不同的对象,因此 e == f 返回 false

3. c == (a + b) 输出 true
  • 原因a + b 的计算涉及拆箱操作,a.intValue()b.intValue() 相加得到一个 int 值,然后与 c 进行比较时,c 也会被拆箱为 int
  • 拆箱过程
    • a + b 被翻译为 a.intValue() + b.intValue()
    • c == (a + b) 被翻译为 c.intValue() == (a.intValue() + b.intValue())
  • 字节码分析
     ALOAD 3INVOKEVIRTUAL java/lang/Integer.intValue ()IALOAD 1INVOKEVIRTUAL java/lang/Integer.intValue ()IALOAD 2INVOKEVIRTUAL java/lang/Integer.intValue ()IIADDIF_ICMPNE L14
    
    • INVOKEVIRTUAL 调用 intValue 方法完成拆箱。
    • 最终比较的是两个 int 值,因此 c == (a + b) 返回 true

4. c.equals(a + b) 输出 true
  • 原因equals 方法比较的是值,而不是引用。a + b 的结果是一个 int,会被自动装箱为 Integer,然后调用 equals 方法进行比较。
  • 字节码分析
     ALOAD 3ALOAD 1INVOKEVIRTUAL java/lang/Integer.intValue ()IALOAD 2INVOKEVIRTUAL java/lang/Integer.intValue ()IIADDINVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;INVOKEVIRTUAL java/lang/Integer.equals (Ljava/lang/Object;)Z
    
    • a + b 的结果被装箱为 Integer
    • equals 方法比较的是两个 Integer 的值,因此返回 true

5. g == (a + b) 输出 true
  • 原因gLong 类型,a + b 的结果是 int 类型。在比较时,a + b 被提升为 long 类型(I2L 指令),然后与 g 的值进行比较。
  • 字节码分析
     ALOAD 7INVOKEVIRTUAL java/lang/Long.longValue ()JALOAD 1INVOKEVIRTUAL java/lang/Integer.intValue ()IALOAD 2INVOKEVIRTUAL java/lang/Integer.intValue ()IIADDI2LLCMPIFNE L18
    
    • I2Lint 提升为 long
    • LCMP 比较两个 long 值,因此 g == (a + b) 返回 true

6. g.equals(a + b) 输出 false
  • 原因equals 方法比较的是对象类型和值。a + b 的结果是 int 类型,会被装箱为 Integer,而 gLong 类型,因此 equals 返回 false
  • 字节码分析
     ALOAD 7ALOAD 1INVOKEVIRTUAL java/lang/Integer.intValue ()IALOAD 2INVOKEVIRTUAL java/lang/Integer.intValue ()IIADDINVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;INVOKEVIRTUAL java/lang/Long.equals (Ljava/lang/Object;)Z
    
    • a + b 的结果被装箱为 Integer
    • equals 方法检查类型不匹配(Long vs Integer),因此返回 false

总结与注意事项

  1. 装箱与拆箱的本质

    • 装箱:将基本类型(如 int)转换为包装类(如 Integer)。底层调用 valueOf 方法。
    • 拆箱:将包装类(如 Integer)转换为基本类型(如 int)。底层调用 intValue 方法。
  2. 缓存机制的影响

    • Integer 的缓存范围是 -128 ~ 127,超出范围会创建新对象。
    • 可以通过 JVM 参数(如 -XX:AutoBoxCacheMax=512)调整缓存范围。
  3. 比较操作的陷阱

    • 使用 == 比较引用类型时,可能会因为缓存或对象创建方式不同而导致结果不符合预期。
    • 推荐使用 equals 方法进行值比较。
  4. 性能问题

    • 频繁的装箱和拆箱操作会导致额外的对象创建和方法调用,影响性能。
    • 在性能敏感的场景下,尽量避免不必要的装箱和拆箱。

扩展思考

在前面的分析中,我们提到 e == f 的结果为 false,因为 321 超出了 Integer 默认的缓存范围(-128 ~ 127),导致每次调用 Integer.valueOf(321) 都会创建新的对象。然而,Java 提供了一种方式来扩展 Integer 的缓存范围,从而改变这一行为。

如何调整缓存范围
Integer 的缓存范围可以通过 JVM 参数 -XX:AutoBoxCacheMax= 进行调整。例如,如果我们希望将缓存范围扩展到 512,可以在启动 JVM 时添加以下参数:

java -XX:AutoBoxCacheMax=512 Test

调整后的效果
当我们将缓存范围扩展到 512 后,e == f 的结果会发生变化:

原因:321 现在位于缓存范围内,因此 Integer.valueOf(321) 会返回缓存中的同一个对象。
输出结果:true

底层原理分析
通过调整缓存范围,Integer.valueOf 方法的行为发生了变化:

如果值在缓存范围内(-128 ~ AutoBoxCacheMax),则返回缓存中的对象。
如果值超出缓存范围,则创建新的 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);
}

其中,IntegerCache.low 和 IntegerCache.high 分别表示缓存范围的下限和上限。默认情况下,IntegerCache.low = -128,IntegerCache.high = 127。通过 JVM 参数 -XX:AutoBoxCacheMax,我们可以动态调整 IntegerCache.high 的值。

http://www.xdnf.cn/news/5151.html

相关文章:

  • 车载电子电器架构 --- 汽车网关概述
  • 【计算机视觉】OpenCV实战项目:Athlete-Pose-Detection 运动员姿态检测系统:基于OpenCV的实时运动分析技术
  • [面试]SoC验证工程师面试常见问题(五)TLM通信篇
  • 引言:Client Hello 为何是 HTTPS 安全的核心?
  • 前端HTMX技术详细解释
  • 第十七次博客打卡
  • AZScreenRecorder最新版:功能强大、操作简便的手机录屏软件
  • 网络编程套接字
  • [白话文] 从百草园RLHF到三味书屋DPO
  • 全栈开发实战:FastAPI + React + MongoDB 构建现代Web应用
  • MCP协议:大模型与外部工具交互的标准化创新方案
  • 从零开始跑通3DGS教程:(四)修改(缩放、空间变换)colmap生成的sfm结果
  • SpringBoot框架开发网络安全科普系统开发实现
  • 分布式事务快速入门
  • 小程序多线程实战
  • 功能齐全的菜谱管理器Tamari
  • [论文阅读]BadPrompt: Backdoor Attacks on Continuous Prompts
  • 23、Next.js:时空传送门——React 19 全栈框架
  • window 显示驱动开发-线性伸缩空间段
  • 简单网络交换、路由二
  • JavaWeb:JDBC
  • 关于ffmpeg的简介和使用总结
  • Kotlin Android LeakCanary内存泄漏检测实战
  • RT-Thread 深入系列 Part 5:物联网与网络应用实战
  • 视觉-语言基础模型作为高效的机器人模仿学习范式
  • 【STM32 学习笔记】I2C通信协议
  • STM32单片机的快速成长路径规划
  • 使用FastAPI和React以及MongoDB构建全栈Web应用04 MongoDB快速入门
  • 《React Native与Flutter:社交应用中用户行为分析与埋点统计的深度剖析》
  • 多层嵌套子查询