前言
这一篇我们继续学习 Java 基础中非常高频的面试题:包装类和自动拆装箱。
在 Java 中,有基本数据类型,比如:
text
int
long
double
boolean
char
也有对应的包装类,比如:
text
Integer
Long
Double
Boolean
Character
刚开始学习时,很多人会觉得它们差不多。比如:
java
int a = 10;
Integer b = 10;
看起来都是保存数字 10。
但是在面试和实际开发中,这里面有很多细节:
- 基本类型和包装类有什么区别?
- 什么是自动装箱和自动拆箱?
Integer a = 127; Integer b = 127;为什么是true?Integer a = 128; Integer b = 128;为什么是false?- 包装类能不能为
null? - 为什么数据库实体类更常用包装类?
这一篇我们就把包装类和自动拆装箱系统梳理一下。
一、基本数据类型和包装类
Java 有 8 种基本数据类型:
| 基本类型 | 包装类 |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
基本类型不是对象,包装类是对象。
比如:
java
int a = 10;
Integer b = 10;
a 是基本类型变量,直接保存数值。
b 是引用类型变量,保存的是对象引用。
二、为什么需要包装类?
既然有基本类型,为什么还要有包装类?
主要原因有几个。
1. 集合不能直接存基本类型
Java 集合中不能直接存基本数据类型。
比如:
java
List<int> list = new ArrayList<>();
这是错误写法。
应该写成:
java
List<Integer> list = new ArrayList<>();
因为泛型要求使用引用类型,不能使用基本类型。
2. 包装类可以表示 null
基本类型不能为 null。
java
int age = null;
这是错误的。
但是包装类可以:
java
Integer age = null;
在项目中,null 经常表示"没有值"或"未填写"。
比如用户年龄没有填写,用 Integer 就更合适。
3. 包装类提供很多工具方法
比如字符串转数字:
java
Integer num = Integer.valueOf("123");
或者:
java
int num = Integer.parseInt("123");
这些方法都在包装类中。
三、什么是自动装箱?
自动装箱就是:
基本类型自动转换成对应的包装类。
示例:
java
Integer num = 10;
这里右边是基本类型 int,左边是包装类 Integer。
编译器会自动帮我们转换,大致相当于:
java
Integer num = Integer.valueOf(10);
这就是自动装箱。
再看集合中的例子:
java
List<Integer> list = new ArrayList<>();
list.add(10);
list 中存的是 Integer,但我们传的是 int。
编译器会自动装箱成:
java
list.add(Integer.valueOf(10));
四、什么是自动拆箱?
自动拆箱就是:
包装类自动转换成对应的基本类型。
示例:
java
Integer num = 10;
int value = num;
这里 num 是 Integer,value 是 int。
编译器会自动转换,大致相当于:
java
int value = num.intValue();
再看一个例子:
java
Integer a = 10;
Integer b = 20;
int result = a + b;
这里 a + b 时,Integer 会自动拆箱成 int,然后进行加法运算。
五、自动拆箱的空指针问题
自动拆箱最容易出现的问题是空指针异常。
看下面代码:
java
Integer num = null;
int value = num;
这段代码会报:
text
NullPointerException
为什么?
因为自动拆箱时底层会调用:
java
num.intValue()
但是 num 是 null,所以调用方法就会空指针。
再看一个项目中常见例子:
java
public void updateStatus(Integer status) {
if (status == 1) {
System.out.println("启用");
}
}
如果调用时传入:
java
updateStatus(null);
这里:
java
status == 1
会触发自动拆箱,相当于:
java
status.intValue() == 1
如果 status 是 null,就会空指针。
更安全的写法:
java
if (Integer.valueOf(1).equals(status)) {
System.out.println("启用");
}
或者:
java
if (status != null && status == 1) {
System.out.println("启用");
}
六、Integer 缓存机制
这是包装类中最经典的面试点。
看下面代码:
java
Integer a = 127;
Integer b = 127;
System.out.println(a == b);
输出:
text
true
再看:
java
Integer a = 128;
Integer b = 128;
System.out.println(a == b);
输出:
text
false
为什么?
因为 Integer 有缓存机制。
默认情况下,Integer 会缓存:
text
-128 到 127
之间的整数对象。
当写:
java
Integer a = 127;
底层相当于:
java
Integer a = Integer.valueOf(127);
对于 -128 到 127 之间的数字,Integer.valueOf() 会从缓存中取对象。
所以:
java
Integer a = 127;
Integer b = 127;
a 和 b 指向同一个缓存对象,a == b 为 true。
但是:
java
Integer a = 128;
Integer b = 128;
128 超出了默认缓存范围,会创建不同对象,所以 a == b 为 false。
七、Integer 比较应该用什么?
包装类比较内容时,建议使用:
java
equals
比如:
java
Integer a = 128;
Integer b = 128;
System.out.println(a.equals(b));
输出:
text
true
不要用:
java
a == b
因为 == 比较的是对象地址。
对于包装类,尤其是 Integer、Long,用 == 很容易因为缓存范围导致结果不一致。
更安全写法:
java
Objects.equals(a, b)
示例:
java
Integer a = null;
Integer b = 10;
System.out.println(Objects.equals(a, b));
Objects.equals 可以避免空指针问题。
八、new Integer 和 valueOf 的区别
虽然现在不推荐使用 new Integer(),但面试可能会问。
java
Integer a = new Integer(127);
Integer b = new Integer(127);
System.out.println(a == b);
输出:
text
false
因为 new Integer(127) 每次都会创建新对象,不会使用缓存。
而:
java
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b);
输出:
text
true
因为 valueOf 会使用缓存。
所以实际开发中不应该手动 new Integer(),而应该使用自动装箱或 Integer.valueOf()。
九、parseInt 和 valueOf 的区别
java
int a = Integer.parseInt("123");
Integer b = Integer.valueOf("123");
区别是:
| 方法 | 返回值 |
|---|---|
parseInt |
int 基本类型 |
valueOf |
Integer 包装类 |
示例:
java
int a = Integer.parseInt("123");
Integer b = Integer.valueOf("123");
如果你需要基本类型,用 parseInt。
如果你需要包装类,用 valueOf。
十、实体类中为什么常用包装类?
在 Spring Boot + MyBatis 项目中,实体类经常这样写:
java
public class Emp {
private Integer id;
private String name;
private Integer gender;
private LocalDate entryDate;
}
为什么不用:
java
private int id;
private int gender;
原因是包装类可以表示 null。
比如新增员工时,前端可能没有传 id,因为 id 由数据库自增生成。
如果用 int,默认值是:
text
0
这会让人分不清:
text
id 是真的为 0
还是根本没传
而 Integer 可以是:
text
null
表示没有值。
所以实体类、DTO、VO 中经常使用包装类。
十一、包装类和数据库字段的关系
数据库中字段可能为空:
sql
gender tinyint unsigned null
如果 Java 中用基本类型:
java
private int gender;
当数据库值是 null 时,映射可能会出问题,或者默认变成 0。
但 0 不一定是合法业务值。
更推荐:
java
private Integer gender;
这样数据库中的 null 可以准确映射成 Java 中的 null。
所以项目开发中:
数据库字段如果允许为空,Java 实体类通常使用包装类。
十二、面试标准回答
如果面试官问:
基本数据类型和包装类有什么区别?什么是自动拆装箱?
可以这样回答:
基本数据类型不是对象,直接保存具体值;包装类是对象,是基本类型对应的引用类型,比如 int 对应 Integer,long 对应 Long。
自动装箱是基本类型自动转换成包装类,比如 Integer num = 10,底层相当于 Integer.valueOf(10)。自动拆箱是包装类自动转换成基本类型,比如 int value = num,底层相当于调用 num.intValue()。
使用包装类时要注意空指针问题。如果包装类对象是 null,自动拆箱时会调用对应方法,比如 intValue(),就会抛出 NullPointerException。
另外,Integer 有缓存机制,默认缓存 -128 到 127。所以 Integer a = 127; Integer b = 127; 用 == 比较是 true,但 128 可能是 false。包装类比较值时,建议使用 equals 或 Objects.equals。
十三、常见问题总结
1. Integer a = 127; Integer b = 127; a == b 是什么?
结果是:
text
true
因为 127 在 Integer 默认缓存范围内。
2. Integer a = 128; Integer b = 128; a == b 是什么?
结果通常是:
text
false
因为 128 超出默认缓存范围,会创建不同对象。
3. 包装类比较能不能用 ==?
不推荐。
包装类是对象,== 比较地址。
比较值建议使用:
java
Objects.equals(a, b)
4. 自动拆箱有什么风险?
如果包装类是 null,自动拆箱会抛出空指针异常。
比如:
java
Integer num = null;
int value = num;
5. 实体类字段用基本类型还是包装类?
通常建议使用包装类。
因为包装类可以表示 null,更适合和数据库字段、前端参数对应。
十四、实际开发建议
实际开发中可以注意下面几点:
- 集合泛型使用包装类,不能使用基本类型
- 实体类、DTO、VO 中数字字段优先使用包装类
- 包装类比较值时使用
Objects.equals - 不要依赖
Integer缓存范围写业务逻辑 - 注意自动拆箱导致的空指针异常
- 金额不要用
Double,优先使用BigDecimal - 字符串转数字时注意捕获
NumberFormatException - 不推荐手动使用
new Integer()
十五、总结
这一篇主要学习了 Java 中的包装类和自动拆装箱。
基本数据类型不是对象,包装类是对象。Java 为 8 种基本类型都提供了对应包装类,比如 int 对应 Integer,long 对应 Long。
自动装箱就是基本类型自动转包装类,自动拆箱就是包装类自动转基本类型。它们让代码写起来更方便,但也带来了空指针风险。
Integer 默认缓存 -128 到 127,所以包装类用 == 比较时可能出现看起来奇怪的结果。实际开发中比较包装类值,建议使用 Objects.equals。
在 Java 后端项目中,实体类字段经常使用包装类,因为包装类可以表示 null,更适合接收前端参数和映射数据库字段。