【Java 开发日记】我们来说一说 Java 自动装箱与拆箱是什么?

【Java 开发日记】我们来说一说 Java 自动装箱与拆箱是什么?

目录

一、核心概念:什么是装箱与拆箱?

1. 手动装箱

2. 手动拆箱

二、什么是自动装箱与拆箱?

1. 自动装箱

2. 自动拆箱

三、实际应用场景举例

四、注意事项与陷阱(非常重要!)

1. 空指针异常

2. 性能消耗

3. 相等比较的陷阱

4. 三目运算符的陷阱

总结


一、核心概念:什么是装箱与拆箱?

要理解“自动”,首先要理解手动的“装箱”和“拆箱”。

Java 是一个面向对象的语言,但为了效率,它同时包含了两种不同的类型系统:

  1. 基本数据类型byteshortintlongfloatdoublecharboolean。它们直接存储“值”,存在于栈内存中,效率高。
  2. 引用类型:所有 Object 的子类。它们存储的是对象的“引用”(地址),实际对象存在于堆内存中。

在某些场景下(例如使用集合类 ArrayListHashMap),我们必须使用引用类型,因为集合只能存储对象。这就产生了在基本类型和其对应的包装类对象之间转换的需求。

包装类:Java 为每一个基本类型都提供了一个对应的“包装类”,将这些基本类型“包装”成对象。

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

1. 手动装箱

将一个基本数据类型的值,包装成对应的包装类对象。

// 手动装箱 int i = 10; Integer integerObj = Integer.valueOf(i); // 方式一:推荐,使用了缓存(后面会讲) // 或者 Integer integerObj2 = new Integer(i); // 方式二:已过时 (Deprecated),不推荐
2. 手动拆箱

从一个包装类对象中,提取出它所包裹的基本数据类型的值。

// 手动拆箱 Integer integerObj = new Integer(10); int j = integerObj.intValue(); // 从 Integer 对象中取出 int 值

二、什么是自动装箱与拆箱?

从 Java 5 开始,为了简化开发,编译器提供了自动装箱自动拆箱的功能。它本质上是一种“语法糖”,编译器在背后自动帮我们插入了转换代码,让我们可以用更简洁的方式编写。

1. 自动装箱

编译器自动将基本数据类型转换为对应的包装类对象。

// 自动装箱 int i = 10; Integer integerObj = i; // 编译器背后实际执行的是:Integer integerObj = Integer.valueOf(i);

在这行代码中,一个 int 类型的变量 i 被直接赋给了一个 Integer 类型的引用。编译器在编译时,会悄悄地调用 Integer.valueOf(i) 来完成转换。

2. 自动拆箱

编译器自动将包装类对象转换为对应的基本数据类型。

// 自动拆箱 Integer integerObj = new Integer(10); int j = integerObj; // 编译器背后实际执行的是:int j = integerObj.intValue();

在这里,一个 Integer 对象被直接赋给了一个 int 类型的变量。编译器在编译时,会悄悄地调用 integerObj.intValue() 来完成转换。


三、实际应用场景举例

自动装箱和拆箱让我们的代码变得非常简洁,尤其是在使用集合类时。

// 在 Java 5 之前,使用 ArrayList 非常麻烦 ArrayList list = new ArrayList(); list.add(Integer.valueOf(1)); // 手动装箱 int value = (Integer) list.get(0)).intValue(); // 取出来是 Object,要强转,再手动拆箱 // 在 Java 5 之后,有了泛型和自动装箱/拆箱 ArrayList<Integer> list = new ArrayList<>(); list.add(1); // 自动装箱:int -> Integer int value = list.get(0); // 自动拆箱:Integer -> int。代码清晰多了!

其他常见场景:

// 1. 方法调用时传递参数 public void processInteger(Integer i) { ... } processInteger(5); // 自动装箱,将 int 5 转为 Integer // 2. 运算符运算时 Integer a = 10; Integer b = 20; int c = a + b; // a 和 b 先自动拆箱为 int,相加后结果再自动装箱赋给 Integer(如果接收类型是Integer) // 等价于:int c = a.intValue() + b.intValue(); // 3. 三目运算符中 boolean flag = true; Integer i = flag ? 100 : 200; // 100和200都会被自动装箱为Integer

四、注意事项与陷阱(非常重要!)

自动装箱虽然方便,但也引入了一些容易忽略的陷阱。

1. 空指针异常

因为自动拆箱实际上是调用了 xxxValue() 方法,如果包装类对象是 null,调用方法就会抛出 NullPointerException

Integer nullInteger = null; int i = nullInteger; // 运行时抛出 NullPointerException! // 背后是:int i = nullInteger.intValue();
2. 性能消耗

虽然单次的装箱/拆箱开销很小,但在循环次数极多(例如上亿次)的场景下,频繁的创建和销毁对象会带来不必要的性能开销。

long start = System.currentTimeMillis(); Long sum = 0L; // 这里用的是包装类 Long,会触发自动装箱 for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; // 每次循环:i自动装箱为Long,然后sum拆箱为long,相加后再装箱为Long } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); // 将 Long sum = 0L 改为 long sum = 0L,性能会有巨大提升。
3. 相等比较的陷阱

第一题:以下的代码会输出什么?

public class Main{ public static void main(Sring[] args){ Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1 == i2); System.out.println(i3 == i4); } }

运行结果:

true false

为什么会出现这样的结果?输出结果表明 i1 和 i2 指向的是同一个对象,而 i3 和 i4 指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是 Integer 的 valueOf 方法的具体实现:

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

其中 IntegerCache 类的实现为:

private static class IntegerCache { static final int high; static final Integer cache[]; static { final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} } 

在通过 valueOf 方法创建 Integer 对象的时候,如果数值在 [-128,127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象

上面的代码中 i1 和 i2 的数值为100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象

第二题:以下的代码会输出什么?

public class Main { public static void main(String[] args) { Double i1 = 100.0; Double i2 = 100.0; Double i3 = 200.0; Double i4 = 200.0; System.out.println(i1==i2); System.out.println(i3==i4); } }

运行结果:

false false

原因: 在某个范围内的整型数值的个数是有限的,而浮点数不是

4. 三目运算符的陷阱

这是一个非常隐蔽的陷阱。

boolean flag = true; Integer i = flag ? 100 : Integer.valueOf(200); // 这没问题,因为 100 和 Integer.valueOf(200) 类型“一致”(都是Integer对象) Integer i = flag ? 100 : null; // 当 flag 为 false 时,会发生什么? // 编译器认为 100 是 int,null 是 Integer。为了类型一致,它会将 100 自动装箱为 Integer,将 null 作为 Integer。 // 所以这里不会报错,i 会被赋值为 null。 int j = flag ? 100 : Integer.valueOf(200); // 这也没问题,因为 Integer.valueOf(200) 会被自动拆箱为 int。 int k = flag ? 100 : null; // 当 flag 为 false 时,会发生 NullPointerException! // 因为编译器需要得到一个 int 类型的结果,所以它会尝试对 `null` 进行自动拆箱,调用 null.intValue()。

总结

  • 自动装箱基本类型 -> 包装类,编译器调用 valueOf()
  • 自动拆箱包装类 -> 基本类型,编译器调用 xxxValue()
  • 优点:简化代码,使基本类型和包装类之间的转换无缝进行。
  • 陷阱
    1. 空指针:包装类为 null 时拆箱会抛 NPE
    2. 性能:在密集循环中可能带来开销。
    3. 比较== 比较包装类是比较地址,应使用 equals 或先拆箱。
    4. 三目运算符:要注意类型的统一,避免意外的自动拆箱。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

Read more

【Java 学习】Comparable接口 和 Comparator接口,掌控排序逻辑解析,深入 Comparable 和 Comparator 的优雅切换

【Java 学习】Comparable接口 和 Comparator接口,掌控排序逻辑解析,深入 Comparable 和 Comparator 的优雅切换

💬 欢迎讨论:如对文章内容有疑问或见解,欢迎在评论区留言,我需要您的帮助! 👍 点赞、收藏与分享:如果这篇文章对您有所帮助,请不吝点赞、收藏或分享,谢谢您的支持! 🚀 传播技术之美:期待您将这篇文章推荐给更多对需要学习Java语言、低代码开发感兴趣的朋友,让我们共同学习、成长! 1. Comparable接口 1.1 为什么要使用Comparable接口 先看代码两组代码: 代码1: importjava.util.Arrays;publicclassMain{publicstaticvoidmain(String[] args){// 创建一个数组String[] strs ={"李华","小明","小红"};// 对数组进行排序Arrays.sort(strs);// 打印System.out.println(Arrays.toString(strs));}} 上述代码可以打印出比较的后的顺序。

By Ne0inhk
全栈开发的演变:从LAMP到MEAN再到现代JavaScript

全栈开发的演变:从LAMP到MEAN再到现代JavaScript

全栈开发者概述 在众多企业中,尤其是创业型公司,人力资源部门在招聘时常常渴望能够找到一位技术上的多面手,即全栈开发者。那么,究竟什么是全栈开发者,他们需要掌握哪些核心技能呢? ◆ 定义与技能要求 传统上,“全栈”开发人员被界定为既能够胜任前端开发,也能进行后端开发工作。然而,在现代软件开发领域,全栈开发者的能力已经超越了这一传统定义。他们不仅需要掌握传统的开发技能,还需要熟悉DevOps工具和技术,如Git、测试以及网站部署等。由于“栈”这一概念涵盖了这些广泛的技术领域,因此,全栈开发人员可被理解为在构建网站的过程中,能够独当一面地处理所有技术问题。 ◆ 技术栈的发展 这些年来,随着技术的发展,某些“栈”已经逐渐淡出人们的视线。其中,LAMP栈(Linux、Apache、MySQL和PHP的组合)曾一度备受瞩目。掌握这四项技术的开发者,被视为能够独立处理网站构建中的各项技术问题。然而,随着时代的演变,全栈的概念已经超越了单纯的技能掌握。 ◆ 角色的变化 LAMP栈的全栈开发人员,确实需要精通Linux、Apache、MySQL和PHP,这些技术构成了网站构建的基础

By Ne0inhk

如何在5分钟内用JavaScript创建专业级韦恩图:venn.js终极指南

如何在5分钟内用JavaScript创建专业级韦恩图:venn.js终极指南 【免费下载链接】venn.jsArea proportional Venn and Euler diagrams in JavaScript 项目地址: https://gitcode.com/gh_mirrors/ve/venn.js 想要在网页上快速创建面积比例准确的韦恩图和欧拉图吗?venn.js是专为数据可视化设计的JavaScript库,让您能够轻松生成专业级的集合关系图表。无论您是数据分析师、前端开发者还是科研工作者,这个强大的工具都能帮助您直观展示数据间的交集和并集关系。🚀 为什么选择venn.js进行数据可视化? venn.js作为专业的JavaScript韦恩图库,具有以下核心优势: * 智能布局算法:自动计算最合适的圆环位置和大小 * 面积比例准确:确保每个区域的面积与数据量成正比 * 完全交互式:支持鼠标悬停、点击等交互操作 * 高度可定制:颜色、样式、文字等均可自由配置 * 轻量且高效:基于D3.js构建,性能优越 快速开始:

By Ne0inhk
飞算 JavaAI:让 Java 开发效率翻倍的秘密武器 #飞算JavaAl炫技赛 #Java开发

飞算 JavaAI:让 Java 开发效率翻倍的秘密武器 #飞算JavaAl炫技赛 #Java开发

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 * 飞算 JavaAI:让 Java 开发效率翻倍的秘密武器 #飞算JavaAl炫技赛 #Java开发 * 一、引言 * 二、Java 开发现状与挑战剖析 * 2.1 Java 语言的辉煌历史 * 2.2 传统 Java 开发的那些 “坑” * 2.2.1 开发周期:比蜗牛爬还慢 * 2.2.2 人力成本:高得离谱 * 2.2.3 代码质量:参差不齐 * 2.2.

By Ne0inhk