通过一个实际的例子,介绍 Java 的自动装箱与拆箱机制

news2024/11/23 21:54:10

Java 中 1000 == 1000 返回 false,但 100 == 100 返回 true,这一现象背后隐藏了 Java 对于对象和基本类型的内存管理机制。为了理解这个现象,我们需要从 Java 的自动装箱与拆箱机制、对象引用和数值缓存策略等角度深入探讨。让我们一步一步通过 JVM 的层面、字节码分析和实例案例来探讨这一问题的根本原因。

自动装箱与拆箱机制

Java 中,== 运算符用于比较两个变量的引用或者它们的值。对于基本类型,== 比较的是数值;而对于对象类型,== 比较的是对象的引用。

在 Java 5 及其之后的版本中,引入了自动装箱(Autoboxing)和拆箱(Unboxing)的特性。自动装箱指的是将基本类型转换为它们对应的包装类对象。例如,当我们使用 Integer a = 100 时,编译器会将基本类型 int 自动转换为 Integer 对象。而拆箱则是相反的过程,例如当需要将一个包装类对象赋值给一个基本类型变量时,编译器会自动将其转换为相应的基本类型。

案例分析:自动装箱与拆箱的作用

考虑以下代码段:

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出 true

在这里,ab 都是通过自动装箱得到的 Integer 对象。由于 Java 对于某些值的包装类对象采用了缓存策略,100 这个数值的包装类对象会从缓存中获取,因此 ab 实际上引用的是同一个对象。因此,a == b 返回 true

但如果我们稍微改动一下数值,将 100 改为 1000

Integer x = 1000;
Integer y = 1000;
System.out.println(x == y); // 输出 false

这次,xy 的比较结果是 false。这是因为数值 1000 并不在 Java 的整数缓存范围内,导致 xy 分别指向不同的 Integer 对象,因此 x == y 返回 false

Java 的数值缓存策略

Java 对于某些包装类对象使用了缓存以提高性能,避免频繁地创建相同值的对象。对于 Integer 类型,Java 会缓存从 -128127 的整数,这个范围内的整数会复用相同的对象。例如:

Integer a = 100;
Integer b = 100;

在这段代码中,100 是在缓存范围内,因此 ab 会指向相同的缓存对象。而对于超出这个范围的数值,例如 1000,则不会复用相同的对象,而是每次创建一个新的对象。

这一缓存机制是在 Integer 类中通过 IntegerCache 实现的。在 Integer 类的内部,存在一个静态的嵌套类 IntegerCache,用来缓存 -128127 范围内的整数对象。来看一下其大致实现方式:

private static class IntegerCache {
    static final Integer[] cache;
    static {
        cache = new Integer[-(-128) + 127 + 1];
        int j = -128;
        for (int k = 0; k < cache.length; k++) {
            cache[k] = new Integer(j++);
        }
    }
    private IntegerCache() {}
}

IntegerCache 的存在使得在自动装箱的过程中,JVM 可以从缓存中获取 -128127 范围内的整数对象,而不是每次都创建新的对象。

字节码和 JVM 层面的分析

为了更深入地理解这一现象,我们可以分析 Java 编译后的字节码,看看 JVM 如何处理这些数值。

编写以下代码:

public class Test {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer x = 1000;
        Integer y = 1000;

        System.out.println(a == b); // true
        System.out.println(x == y); // false
    }
}

使用 javap 工具查看编译后的字节码:

javac Test.java
javap -c Test

我们可以看到类似如下的字节码输出:

0: bipush        100
2: invokestatic  #2  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush        100
8: invokestatic  #2  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: sipush        1000
15: invokestatic  #2  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: sipush        1000
22: invokestatic  #2  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore        4
26: getstatic     #3  // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_1
30: aload_2
31: if_acmpne     38
34: iconst_1
35: goto          39
38: iconst_0
39: invokevirtual #4  // Method java/io/PrintStream.println:(Z)V
42: getstatic     #3  // Field java/lang/System.out:Ljava/io/PrintStream;
45: aload_3
46: aload         4
48: if_acmpne     55
51: iconst_1
52: goto          56
55: iconst_0
56: invokevirtual #4  // Method java/io/PrintStream.println:(Z)V

在这段字节码中,可以看到整数 1001000 都是通过 bipushsipush 指令推入操作数栈中,随后通过 invokestatic 调用 Integer.valueOf 方法来进行自动装箱。Integer.valueOf 方法的实现决定了是否使用缓存对象。

具体来看 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);
}

对于在缓存范围内的整数,会直接返回缓存中的对象;而对于不在缓存范围内的整数,则会创建一个新的 Integer 对象。因此,在比较 100 时,ab 引用的是相同的对象,而在比较 1000 时,xy 引用的是不同的对象。

引用类型与基本类型的比较

在 Java 中,== 运算符对于基本类型和引用类型的处理方式是不同的。对于基本类型,== 比较的是它们的数值;而对于引用类型,== 比较的是对象在内存中的引用是否相同。

来看一个实际的例子,以便更好地理解这一区别:

Integer a = 128;
Integer b = 128;
int c = 128;

System.out.println(a == b); // 输出 false
System.out.println(a == c); // 输出 true

在这段代码中,ab 都是 Integer 对象,它们的值为 128,但由于 128 超出了缓存范围,因此 ab 指向不同的对象。因此,a == b 返回 false

a == c 返回 true,是因为 c 是一个基本类型变量。在比较时,a 会被拆箱为基本类型 int,然后与 c 进行数值比较,结果相同,因此返回 true

JVM 的对象池和性能优化

Java 的这种缓存机制不仅适用于 Integer,还应用于其他包装类,例如 ShortByteCharacter 等。JVM 之所以引入这种缓存机制,是为了优化性能,因为创建对象是一个相对昂贵的操作,而对于频繁使用的相同数值,通过缓存可以减少对象的创建,降低内存的使用。

这种优化在实际应用中非常重要。例如在大型系统中,经常需要处理大量的整数计算和比较。如果每次都创建新的 Integer 对象,会对内存和垃圾回收造成很大的压力。而通过缓存,可以显著提高系统的性能。

举一个实际场景,假设我们有一个系统需要频繁计算和存储年龄数据。年龄一般都是 0 到 120 的整数,如果每次都创建新的 Integer 对象,会浪费大量的内存资源。通过缓存,这些常用数值可以复用,极大地提高了系统的效率。

注意事项:避免常见陷阱

理解了自动装箱、拆箱以及缓存机制的工作原理后,我们可以避免一些常见的陷阱。在使用 IntegerLong 等包装类进行比较时,应该尽量使用 equals 方法,而不是 == 运算符。因为 equals 方法会比较对象的数值,而 == 运算符比较的是引用。例如:

Integer a = 1000;
Integer b = 1000;

System.out.println(a.equals(b)); // 输出 true
System.out.println(a == b);      // 输出 false

在这段代码中,a.equals(b) 返回 true,因为 equals 方法比较的是对象的数值。而 a == b 返回 false,因为它比较的是对象的引用。

同样地,在处理浮点数时也应该小心。例如 DoubleFloat 类同样存在类似的缓存机制,但其范围和处理方式有所不同。在使用这些包装类时,如果需要进行数值比较,使用 equals 方法是更为稳妥的做法。

实际应用:数值比较与数据处理

在企业应用开发中,可能会遇到大量的数据需要比较和处理。例如,在财务系统中,需要对多个账户的余额进行比较。假设余额使用 Double 包装类存储,那么直接使用 == 运算符比较两个余额可能会产生错误的结果,尤其是在数值超出缓存范围的情况下。为了避免这样的陷阱,可以使用 Double.equals 方法进行比较,确保比较的是实际的数值。

Double balance1 = 1000.0;
Double balance2 = 1000.0;

if (balance1.equals(balance2)) {
    System.out.println("Balances are equal.");
} else {
    System.out.println("Balances are not equal.");
}

这样可以确保在比较时不会因为引用不同而导致错误的判断。

深入理解 Java 的设计意图

Java 引入自动装箱和拆箱的主要目的是为了简化基本类型和包装类之间的转换,使得代码更加简洁易读。但这一特性也引入了一些潜在的陷阱,特别是在进行引用类型比较时,容易因为对象的引用不同而导致比较结果与预期不符。

通过数值缓存机制,Java 试图在性能和易用性之间取得平衡。对于频繁使用的小整数(如 -128127),缓存机制可以减少对象创建,提升性能。而对于超出缓存范围的数值,虽然需要创建新的对象,但通常这些数值出现的频率较低,因此对性能的影响相对较小。

Java

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

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

相关文章

电脑怎么卸载软件?学会这6个卸载软件技巧就够了(精选)

电脑怎么卸载软件&#xff1f;在日常的办公生活中&#xff0c;我们需要下载一些工具来辅助工作&#xff0c;当不需要这些工具的时候&#xff0c;我们就需要卸载这些软件了。很多小伙伴表示卸载软件卸载不干净&#xff0c;还是回残留一些文件&#xff0c;或者是卸载不了&#xf…

Verilog开源项目——百兆以太网交换机(九)表项管理模块设计

Verilog开源项目——百兆以太网交换机&#xff08;九&#xff09;表项管理模块设计 &#x1f508;声明&#xff1a;未经作者允许&#xff0c;禁止转载 &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN主页 &#x1f511;全新原创以太网交换机项目&#xff0c;Blog内容将聚焦整…

新型僵尸网络针对 100 个国家发起 30 万次 DDoS 攻击

近日&#xff0c;网络安全研究人员发现了一个名为 Gorilla&#xff08;又名 GorillaBot&#xff09;的新僵尸网络恶意软件家族&#xff0c;它是已泄露的 Mirai 僵尸网络源代码的变种。 网络安全公司 NSFOCUS 在上个月发现了这一活动&#xff0c;并称该僵尸网络在今年 9 月 4 日…

【Java 循环控制实例详解【While do... while】】

Java 循环控制详解【While & do… while】 在 Java 中&#xff0c;循环控制是程序设计中非常重要的部分&#xff0c;主要包括 while 循环和 do...while 循环。本文将详细介绍这两种循环的基本语法、执行流程及相关示例。 1. while 循环控制 基本语法 循环变量初始化; wh…

在uniapp中实现长按聊天对话框可以弹出对话框然后可以删除该条对话,单击可以进入该条对话框的对话页面

效果展示 效果描述 长按【大于1s】某一条对话框会弹出一个对话框&#xff0c;点击确定按钮就可以将当前对话框从列表中进行删除&#xff0c;如果点击取消则不做额外操作。 如果只是点击了一下&#xff0c;时间【小于1s】的情况下会直接引入到与该用户的对话框详情页面。 代码…

ai绘画变现方式全解析,教你如何通过AI绘画赚钱

*AI绘画变现方式全解析&#xff0c;教你如何通过AI绘画赚钱* *为什么选择AI绘画&#xff1f;* 你是否曾经梦想过成为一名画家&#xff0c;但现实却让你无从下手? 或者你已经是一位艺术家&#xff0c;但苦于作品没能带来足够的收入&#xff1f;随着AI技术的飞速发展&#xff…

IDEA没有代码自动提示问题的解决

一、问题描述 如图&#xff0c;博主输入new Hash 没有提示出HashMap之类的api 原因在于&#xff1a;不小心打开了idea的省电模式&#xff0c;在这个模式下&#xff0c;idea是不会为我们提供自动提示的 二、问题解决 在File中找到 Power Save Mode选项&#xff0c;将它关闭即…

【西电电路实验】1. 仪器的使用(电子线路 III 电院)

文章目录 前言一、实验原理二、实验过程1. [高频信号源DSG3030 数据手册](https://www.rigol.com/Images/DSG3000_DataSheet_CN_tcm4-3558.pdf)2. [信号源 SDS 2320X 数据手册](https://www.siglent.com/u_file/download/24_05_29/SDS2000X%20HD_Datasheet_CN02A.pdf#:~:textSD…

MySQL多表查询:标量子查询

先看我的emp表结构 emp表 子查询基本语法 select * from t1 where column1 (select column1 from t2);例子1&#xff1a;查询"销售部" 的所有员工信息 这个可以先拆解为两个 a.查询"销售部"的部门ID select id from dept where name 销售部; b. 根…

2024重生之回溯数据结构与算法系列学习(11)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】

欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 [点击箭头指向的专栏名即可闪现] 专栏跑道一 ➡️ MYSQL REDIS Advance operation 专栏跑道二➡️ 24 Network Security -LJS ​ ​ ​ 专栏跑道三 ➡️HCIP&#xff1b;H3C-SE;CCIP——…

算法:724.寻找数组的中心下标

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;前缀和&#xff09; 根据题意&#xff0c;我们可以将数组看成三个部分 [left] [mid] [right] 我们只需要[left]区间内的元素和等于[right]区间的元素和即可&#xff0c;此时mid就是中心下标 那么我们可以借助前缀和思…

怎么把录音转换成文字?七大不收费的录音转文字软件推荐,亲测好用!

怎么把录音转换成文字&#xff1f;录音转文字软件是一种可以将音频文件转换成文本的工具&#xff0c;甚至可以在录制音频的同时实时生成文本。音频文件是存储信息的常用方式之一&#xff0c;广泛应用于在线课程、长时间的讲座、商务会议以及产品介绍等场合&#xff0c;音频内容…

ThreadPoolExecutor的源码解析

ThreadPoolExecutor的源码解析 线程池的核心属性 ctl:当前的ctl就是一个int类型的数值,内部是基于AtomicInteger套了一层,进行运算时,是原子性的 ctl表示的线程池的两种核心状态: 线程池的状态: ctl的高3位标识线程池的状态工作线程的状态:ctl的低29位,表示工作线程的个数 pri…

FlagVNE]——用于虚拟网络嵌入的灵活、可通用的强化学习框架

介绍 论文地址&#xff1a;https://arxiv.org/pdf/2404.12633 网络虚拟化&#xff08;NV&#xff09;是一种创新技术&#xff0c;在 5G 网络和云计算等领域日益受到关注。NV 可通过网络切片和共享基础设施在同一物理网络上部署多个用户提交的虚拟网络请求&#xff08;VNR&…

性能测试-JMeter(1)

性能测试工具 主流性能测试工具LoadrunnerJMeter JMeter环境安装JMeter功能概要JDK常用文件目录介绍JMeter元件和组件介绍元件的基本介绍组件的基本介绍 JMeter元件作用域和执行顺序JMeter第一个案例线程组HTTP请求查看结果树 JMeter参数化&#xff08;重点&#xff09;用户定义…

02_InFluxDb

InFluxDb 初始化初始化流程 交互InFluxDbWebUI交互 数据模型行协议添加标签数据格式 数据类型空格索引 初始化 初始化流程 用户 密码 组织名称 Bucket—mysql里面的数据库概念 交互InFluxDb 暂用了8086端口.提供了 http api WebUI交互 略... 数据模型 这是mysql里面的表…

1500元买哪款显卡好?对比一下,差别明显

在游戏过程中&#xff0c;显卡负责渲染游戏画面&#xff0c;将其转化为可视化的图像&#xff0c;并快速显示在屏幕上&#xff0c;确保游戏运行的流畅性和画面的质量。所以对于游戏电脑来说&#xff0c;显卡的重要性尤为突出。虽说在最近几年&#xff0c;显卡市场的“消费升级”…

算法:前缀和算法模版

一维前缀和 题目 链接&#xff1a;一维前缀和模版题 思路分析 一&#xff1a;暴力O(q * N) 对于每一次询问&#xff0c;我们都可以用一个循环计算[l,r]区间内的元素和&#xff0c; 时间复杂度&#xff0c;O(q * N) 每一次计算一个区间都需要去循环一次&#xff0c;这是不是…

2024年中国研究生数学建模什么时候出成绩(附避坑指南)

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 今年的华为杯已经于2024年9月20日——2024年9月25日完成&#xff0c;相信大家下…

40 C 语言结构体:结构体数据类型、结构体变量、访问结构体成员、结构体指针

目录 1 为什么需要结构体 2 什么是结构体 3 声明结构体类型 3.1 语法格式 3.2 案例演示 3.2.1 学生信息结构体 3.2.2 通讯录条目结构体 3.2.3 猫咪结构体 4 声明结构体变量 4.1 什么是结构体变量 4.2 声明结构体变量的常见方式 5 结构体和结构体变量的区别与联系 …