包装类、自动装箱与自动拆箱 | JavaSE
包装类、自动装箱与自动拆箱
一、学习目标
完成本章后,你应该能够:
- 能够说明 Java 为什么为 8 种基本数据类型提供对应的包装类。
- 能够写出 8 种基本类型与包装类型之间的对应关系。
- 能够解释自动装箱(Autoboxing)和自动拆箱(Unboxing)。
- 能够正确使用
Integer.valueOf()、Integer.parseInt()、Integer.toString()等常用 API。 - 能够解释为什么泛型中可以写
Integer,却不能直接写int。 - 能够判断包装类参与运算时是否发生了自动拆箱。
- 能够识别
null自动拆箱导致的NullPointerException。 - 能够理解包装类对象比较时
==与equals()的区别。 - 能够避免依赖 Integer 缓存进行数值比较。
二、核心知识
2.1 基本数据类型与对象世界
Java 中已经学习过 8 种基本数据类型:
byte
short
int
long
float
double
char
boolean
例如:
int age = 18;
double score = 95.5;
boolean passed = true;
这些都是:
基本数据类型(Primitive Type)
而 Java 又是一门高度面向对象的语言,大量 API、集合、泛型机制主要围绕:
引用数据类型(Reference Type)
工作。
例如后面会大量使用:
ArrayList<String>
ArrayList<Student>
但是下面这种代码是不允许的:
// ArrayList<int> list = new ArrayList<>();
原因在于:
Java 泛型的类型实参不能直接使用基本数据类型。
那么问题来了:
如果我就是想把整数放进泛型集合怎么办?
Java 的解决方案就是:
包装类(Wrapper Class)
2.2 什么是包装类
包装类可以把基本类型的数据表示为对应的对象类型。
8 种基本数据类型都有对应的包装类:
| 基本类型 | 包装类 |
| --------- | ----------- |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
其中要特别注意两个命名:
int → Integer
char → Character
并不是简单首字母大写。
2.3 包装类解决了什么问题
假设我们要保存多个整数:
ArrayList<Integer> numbers =
new ArrayList<>();
这里泛型参数使用:
Integer
而不是:
int
因为:
Integer 是引用类型
int 是基本类型
于是包装类就在:
基本数据类型
↕
面向对象 / 泛型 / 集合体系
之间建立了一座桥梁。
2.4 手动装箱
过去可以显式创建 Integer 对象:
Integer number = Integer.valueOf(100);
这里:
int 100
↓
Integer 对象
就是一种:
装箱(Boxing)
过程。
在 JDK 21 中,推荐使用:
Integer.valueOf(100);
而不是:
// 不作为现代 Java 主方案
// Integer number = new Integer(100);
现代 Java 中通常甚至不需要手动调用 valueOf(),因为语言已经支持:
自动装箱
2.5 自动装箱
自动装箱(Autoboxing)表示:
Java 可以在需要包装类型的上下文中,自动把基本类型转换成对应的包装类型。
例如:
Integer number = 100;
左边需要:
Integer
右边:
100
本来是:
int
Java 自动完成:
int
↓
Integer
所以可以近似理解成:
Integer number = Integer.valueOf(100);
2.6 自动拆箱
自动拆箱(Unboxing)正好相反:
Java 可以把包装类对象自动转换成对应的基本类型值。
例如:
Integer number = 100;
int value = number;
这里:
Integer
↓
int
发生了自动拆箱。
概念上可以理解成:
int value = number.intValue();
2.7 装箱和拆箱对应关系
int
│
│ 自动装箱
▼
Integer
│
│ 自动拆箱
▼
int
其他包装类同理:
double ↔ Double
boolean ↔ Boolean
char ↔ Character
long ↔ Long
2.8 包装类与集合
这是包装类最经典的应用场景之一。
import java.util.ArrayList;
public class WrapperDemo {
public static void main(String[] args) {
ArrayList<Integer> numbers =
new ArrayList<>();
numbers.add(100);
numbers.add(200);
numbers.add(300);
int number = numbers.get(0);
System.out.println(number);
}
}
看起来:
numbers.add(100);
传入的是 int。
实际上集合要求:
Integer
于是发生:
100
↓ 自动装箱
Integer
而:
int number = numbers.get(0);
get() 得到:
Integer
赋值给:
int
于是发生:
Integer
↓ 自动拆箱
int
完整过程:
int
↓ boxing
Integer
↓ 存入集合
Integer
↓ 从集合取出
int
↓ unboxing
基本类型继续计算
2.9 包装类也可以参与运算
例如:
Integer a = 10;
Integer b = 20;
int result = a + b;
System.out.println(result);
+ 是数值运算。
因此 a、b 会参与拆箱:
Integer a
↓
int 10
Integer b
↓
int 20
10 + 20
↓
30
甚至可以写:
Integer number = 10;
number++;
这背后会涉及:
拆箱
↓
数值运算
↓
再次装箱
因此包装类虽然写起来很像基本类型,但:
包装类依然是引用类型,不能忘记它本质上是对象。
三、使用方法
3.1 基本类型转字符串
例如:
int number = 23;
想得到:
"23"
可以:
String text =
Integer.toString(number);
结果:
"23"
3.2 String.valueOf()
实际开发中还经常使用:
String text =
String.valueOf(number);
例如:
int age = 18;
String result =
String.valueOf(age);
System.out.println(result);
3.3 字符串转 int
这是非常重要的 API。
假设用户输入:
String text = "98";
虽然看起来像数字,
但类型仍然是:
String
不能直接:
// int result = text;
可以使用:
int result =
Integer.parseInt(text);
结果:
98
3.4 parseInt() 与 valueOf()
下面两个 API 很容易混淆:
Integer.parseInt("98");
返回:
int
而:
Integer.valueOf("98");
返回:
Integer
对比:
| API | 返回类型 |
| ------------------------ | --------- |
| Integer.parseInt("98") | int |
| Integer.valueOf("98") | Integer |
例如:
int a = Integer.parseInt("98");
Integer b = Integer.valueOf("98");
如果目标就是:
得到基本类型 int
parseInt() 语义更加直接。
3.5 Double 同样支持转换
例如:
String text = "98.8";
double value =
Double.parseDouble(text);
System.out.println(value + 2);
结果:
100.8
也可以:
Double value =
Double.valueOf(text);
返回包装对象。
3.6 非法数字字符串
下面代码:
int number =
Integer.parseInt("Java");
无法把:
Java
解释成整数。
于是运行时会抛出:
NumberFormatException
这正好和前面异常章节连接起来。
因此:
String → 数值
并不是无条件成功。
实际程序需要考虑:
输入是否合法?
四、原理与进阶
4.1 null 自动拆箱的危险
这是包装类非常重要的坑。
Integer number = null;
int value = number;
表面上只是:
Integer → int
但拆箱本质类似:
number.intValue();
而:
number == null
因此相当于:
在 null 上调用实例方法
会产生:
NullPointerException
所以:
Integer count = null;
if (count > 0) {
}
同样可能出问题。
为什么?
因为比较:
count > 0
需要先把 Integer 拆成 int。
4.2 基本类型和包装类型默认值不同
成员变量中:
private int age;
默认值:
0
而:
private Integer age;
默认值:
null
所以在业务模型中:
int
和:
Integer
有时表达不同语义。
例如:
int age = 0
只能表达:
年龄值为 0
而:
Integer age = null
还可以额外表达:
年龄尚未提供
这也是以后数据库实体类、DTO、JSON 映射中经常使用包装类型的重要原因之一。
4.3 Integer 对象不要用 == 比数值
考虑:
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
可能得到:
true
再:
Integer c = 130;
Integer d = 130;
System.out.println(c == d);
常见 JDK 实现中会得到:
false
为什么?
因为:
==
如果两边都是引用类型,比较的是:
引用是否指向同一个对象
不是单纯比较数值。
4.4 Integer 缓存
Java 对部分常用装箱值存在对象复用规则。
对于一定范围内的常量装箱值,Java 规范保证重复装箱得到相同引用。
所以:
Integer a = 100;
Integer b = 100;
可能共享包装对象。
而对于:
Integer a = 130;
Integer b = 130;
不能依赖:
a == b
判断数值是否相等。
正确思路:
a.equals(b)
或者如果已经拆箱:
a.intValue() == b.intValue()
核心规则是:
包装类比较数值时,不要把对象身份比较
==当成数值比较。
4.5 为什么 valueOf() 比构造器更合适
例如:
Integer.valueOf(100);
允许 Java:
复用已有包装对象
而直接创建一个全新包装对象会失去这种优化空间。
因此现代 Java API 已经明确引导开发者使用:
valueOf()
自动装箱
而不是手动调用 Integer 构造器。
4.6 自动装箱并没有把 int 变成“同一种类型”
下面:
int a = 10;
Integer b = 10;
虽然 Java 可以自动转换,
但是:
int
和:
Integer
仍然是两个不同类型:
int → primitive type
Integer → reference type
自动装箱只是:
语言提供了自动转换能力
并不是:
基本类型与包装类从此完全没有区别
五、实践应用
5.1 集合中存储数字
ArrayList<Integer> scores =
new ArrayList<>();
scores.add(90);
scores.add(95);
scores.add(88);
这是以后非常常见的写法。
5.2 接收文本参数
Web、控制台、文件中的数据经常最初表现为:
String
例如:
"18"
"99.5"
业务计算前需要:
String
↓ parse
int / double
例如:
int age =
Integer.parseInt("18");
double score =
Double.parseDouble("99.5");
5.3 可空业务字段
例如用户注册时:
age 没有填写
如果使用:
Integer age;
可以用:
null
表达:
未填写
这与:
0
是不同业务状态。
六、常见问题
6.1 为什么泛型不能写 int?
因为泛型类型参数使用的是引用类型体系。
所以:
ArrayList<int>
不合法。
应该:
ArrayList<Integer>
6.2 自动装箱是不是必须自己调用 valueOf()?
不是。
例如:
Integer value = 100;
Java 会自动执行装箱转换。
6.3 自动拆箱是不是必须调用 intValue()?
通常不需要。
例如:
Integer value = 100;
int number = value;
会自动拆箱。
6.4 Integer 可以为 null 吗?
可以。
因为:
Integer 是引用类型
所以:
Integer number = null;
合法。
6.5 int 可以为 null 吗?
不可以。
// int number = null;
不能通过编译。
6.6 为什么 null 拆箱会出现 NullPointerException?
因为拆箱需要从包装对象中取得基本类型值。
例如:
Integer → int
本质需要访问这个对象。
如果引用为:
null
自然无法取得对应值。
6.7 Integer.parseInt() 和 Integer.valueOf() 有什么区别?
核心区别:
parseInt()
→ int
valueOf()
→ Integer
6.8 为什么 Integer == 比较很危险?
因为 Integer 是对象。
==
可能变成:
对象引用比较
而且包装对象还存在缓存行为。
因此数值相等通常使用:
a.equals(b)
6.9 可以继续使用 new Integer(...) 吗?
在现代 Java 中不应该把它作为主方案。
优先:
Integer.valueOf(...)
或者直接:
Integer number = 100;
七、练习与验收
7.1 知识问答
- 为什么 Java 为基本类型设计包装类?
int对应什么包装类?char对应什么包装类?- 8 种基本类型分别对应哪些包装类?
- 什么是自动装箱?
- 什么是自动拆箱?
Integer number = 100;发生了什么?int number = integer;发生了什么?- 为什么
ArrayList<int>不合法? Integer.parseInt()返回什么类型?Integer.valueOf()返回什么类型?Integer.toString()解决什么问题?- 非法字符串执行
parseInt()可能产生什么异常? - 为什么包装类可以保存
null? - 为什么
null自动拆箱可能产生 NPE? - 为什么不能依赖
Integer的==比较数值?
7.2 代码阅读
阅读:
public class WrapperRead01 {
public static void main(String[] args) {
Integer a = 20;
int b = a;
Integer c = b + 10;
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
回答:
Integer a = 20发生什么转换?int b = a发生什么转换?b + 10的结果是什么类型?- 赋给
c时又发生什么?
阅读:
public class WrapperRead02 {
public static void main(String[] args) {
Integer count = null;
if (count > 0) {
System.out.println("positive");
}
}
}
不运行程序回答:
- 代码能否通过编译?
- 比较前需要发生什么?
- 运行时可能产生什么异常?
- 根本原因是什么?
7.3 手写代码
任务一:基本类型与字符串转换
给定:
int number = 98;
要求:
- 使用
Integer.toString()转成字符串。 - 使用
String.valueOf()再实现一次。 - 再将
"123"转换回int。
任务二:包装类集合
创建:
ArrayList<Integer>
保存:
90
85
100
95
遍历并计算总分。
观察:
add 时发生什么
get 后参与加法时发生什么
任务三:parseInt 与 valueOf
分别使用:
Integer.parseInt("100")
Integer.valueOf("100")
保存返回值。
要求写出两个变量的准确类型。
7.4 Debug
import java.util.ArrayList;
public class WrapperDebug {
public static void main(String[] args) {
// ArrayList<int> list =
// new ArrayList<>();
Integer count = null;
int value = count;
System.out.println(value);
}
}
要求:
- 为什么
ArrayList<int>不合法? Integer count = null是否合法?int value = count会发生什么?- 问题发生在编译期还是运行期?
- 应该如何根据业务语义修复?
再分析:
Integer a = 130;
Integer b = 130;
if (a == b) {
System.out.println("相等");
}
回答:
a == b比较的到底是什么?- 为什么这不是可靠的数值比较方法?
- 应该如何改写?
7.5 综合训练
实现一个成绩录入程序。
原始数据:
String[] scores = {
"90",
"88",
"100",
"95"
};
要求:
- 将每个字符串转换成整数。
- 保存到
ArrayList<Integer>。 - 计算总分。
- 计算平均分。
- 输出最高分。
- 分析其中发生了哪些:
- 字符串转换
- 自动装箱
- 自动拆箱
- 修改其中一个字符串为
"Java",观察异常并说明原因。
7.6 本章验收
- [ ] 能闭卷写出 8 种基本类型与包装类对应关系。
- [ ] 能解释自动装箱与自动拆箱。
- [ ] 能独立使用
Integer.parseInt()。 - [ ] 能独立使用
Integer.valueOf()。 - [ ] 能解释二者返回类型的区别。
- [ ] 能解释为什么泛型使用
Integer而不是int。 - [ ] 能发现 null 拆箱导致的 NPE。
- [ ] 能解释 Integer 的
==为什么不是可靠的数值比较。 - [ ] 能独立完成 String → int → Integer 集合 → int 运算的完整流程。