【Java八股|第10篇】Java 中的包装类和自动拆装箱

前言

这一篇我们继续学习 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;

这里 numIntegervalueint

编译器会自动转换,大致相当于:

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()

但是 numnull,所以调用方法就会空指针。

再看一个项目中常见例子:

java 复制代码
public void updateStatus(Integer status) {
    if (status == 1) {
        System.out.println("启用");
    }
}

如果调用时传入:

java 复制代码
updateStatus(null);

这里:

java 复制代码
status == 1

会触发自动拆箱,相当于:

java 复制代码
status.intValue() == 1

如果 statusnull,就会空指针。

更安全的写法:

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);

对于 -128127 之间的数字,Integer.valueOf() 会从缓存中取对象。

所以:

java 复制代码
Integer a = 127;
Integer b = 127;

ab 指向同一个缓存对象,a == btrue

但是:

java 复制代码
Integer a = 128;
Integer b = 128;

128 超出了默认缓存范围,会创建不同对象,所以 a == bfalse

七、Integer 比较应该用什么?

包装类比较内容时,建议使用:

java 复制代码
equals

比如:

java 复制代码
Integer a = 128;
Integer b = 128;

System.out.println(a.equals(b));

输出:

text 复制代码
true

不要用:

java 复制代码
a == b

因为 == 比较的是对象地址。

对于包装类,尤其是 IntegerLong,用 == 很容易因为缓存范围导致结果不一致。

更安全写法:

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 对应 Integerlong 对应 Long

自动装箱是基本类型自动转换成包装类,比如 Integer num = 10,底层相当于 Integer.valueOf(10)。自动拆箱是包装类自动转换成基本类型,比如 int value = num,底层相当于调用 num.intValue()

使用包装类时要注意空指针问题。如果包装类对象是 null,自动拆箱时会调用对应方法,比如 intValue(),就会抛出 NullPointerException

另外,Integer 有缓存机制,默认缓存 -128127。所以 Integer a = 127; Integer b = 127;== 比较是 true,但 128 可能是 false。包装类比较值时,建议使用 equalsObjects.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,更适合和数据库字段、前端参数对应。

十四、实际开发建议

实际开发中可以注意下面几点:

  1. 集合泛型使用包装类,不能使用基本类型
  2. 实体类、DTO、VO 中数字字段优先使用包装类
  3. 包装类比较值时使用 Objects.equals
  4. 不要依赖 Integer 缓存范围写业务逻辑
  5. 注意自动拆箱导致的空指针异常
  6. 金额不要用 Double,优先使用 BigDecimal
  7. 字符串转数字时注意捕获 NumberFormatException
  8. 不推荐手动使用 new Integer()

十五、总结

这一篇主要学习了 Java 中的包装类和自动拆装箱。

基本数据类型不是对象,包装类是对象。Java 为 8 种基本类型都提供了对应包装类,比如 int 对应 Integerlong 对应 Long

自动装箱就是基本类型自动转包装类,自动拆箱就是包装类自动转基本类型。它们让代码写起来更方便,但也带来了空指针风险。

Integer 默认缓存 -128127,所以包装类用 == 比较时可能出现看起来奇怪的结果。实际开发中比较包装类值,建议使用 Objects.equals

在 Java 后端项目中,实体类字段经常使用包装类,因为包装类可以表示 null,更适合接收前端参数和映射数据库字段。

相关推荐
zfoo-framework2 小时前
mongo最佳实战(from mongo中文社区)
java
深盾科技_Virbox2 小时前
加密狗授权能力选型:从授权模型到全生命周期管理
java·网络·数据库
. . . . .3 小时前
Egg框架深入
java·开发语言
RainCity3 小时前
Java Swing 自定义组件库分享(十三)
java·笔记·后端
livemetee4 小时前
【关于Spring声明式事务】
java·后端·spring
倒流时光三十年4 小时前
Java 内存模型(JMM)通俗解释
java·开发语言
码兄科技4 小时前
Java AI智能体开发实战:从零构建企业级智能应用指南
java·开发语言·人工智能
2401_859506244 小时前
AIGC赋能大漆摆件设计:从痛点分析到技术架构与实战验证
java·大数据·人工智能
剑挑星河月4 小时前
54.螺旋矩阵
java·算法·leetcode·矩阵