网站公告

本次更新:没啥更新的

“钱塘江上潮信起,今日方知我是我”

Skip to content

包装类 ​

字数: 0 字 时长: 0 分钟

Java 包装类 ​

字数: 0 字 时长: 0 分钟

1. 包装类概述 ​

Java 中存在:

text
基本数据类型
Primitive Type

例如:

java
int age = 18;

double price = 99.9;

boolean enabled = true;

但基本数据类型本身并不是对象。

为了让基本数据类型也能够以:

text
对象

的形式参与 Java 的面向对象体系,Java 为每一种基本数据类型提供了对应的:

text
包装类
Wrapper Class

例如:

java
int
    ↓
Integer

于是:

java
int age = 18;

也可以表示为:

java
Integer age = 18;

可以简单理解:

text
基本数据类型
    ↓
包装成对象
    ↓
包装类

1.1 基本数据类型与包装类 ​

Java 的 8 种基本数据类型都有对应的包装类:

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

其中需要特别注意:

text
int
    ↓
Integer


char
    ↓
Character

并不是:

text
Int

Char

另外 Java 还有:

java
Void

这个特殊类,用于表示:

text
void

相关的类型语义。

但它和其他包装类不太一样,平时基本不会直接创建或使用:

java
Void

对象。

1.2 为什么需要包装类 ​

既然已经有:

java
int

double

boolean

为什么还需要:

java
Integer

Double

Boolean

?

主要原因是:

Java 中很多 API 和面向对象机制只能操作对象,而不能直接操作基本数据类型。

例如泛型:

java
List<Integer> list =
        new ArrayList<>();

不能写:

java
// 编译错误
// List<int> list =
//         new ArrayList<>();

因为 Java 泛型要求类型参数是:

text
引用类型

而:

java
int

属于基本数据类型。

所以需要:

text
int
    ↓
Integer

再例如:

java
Map<Long, User>

这里:

java
Long

也是:

text
long

对应的包装类。

包装类还提供了很多基本类型本身没有的方法:

java
Integer.parseInt()

Integer.valueOf()

Integer.compare()

Integer.max()

Double.parseDouble()

Boolean.parseBoolean()

所以包装类不仅仅是:

text
给基本类型套一层对象

它还提供了:

text
类型转换

解析

比较

常量

工具方法

等能力。

1.3 包装类的继承关系 ​

大部分数字包装类继承:

java
java.lang.Number

关系可以简单理解:

text
Object
├── Number
│   ├── Byte
│   ├── Short
│   ├── Integer
│   ├── Long
│   ├── Float
│   └── Double
│
├── Character
└── Boolean

Number 是一个抽象类。

它定义了一些将数字转换为基本类型的方法:

java
intValue()

longValue()

floatValue()

doubleValue()

例如:

java
Integer value = 100;

double number =
        value.doubleValue();

结果:

text
100.0

2. 装箱与拆箱 ​

基本数据类型和包装类之间经常需要进行转换。

例如:

text
int
↕
Integer

这个过程分别叫:

text
装箱
Boxing


拆箱
Unboxing

2.1 装箱 ​

把:

text
基本数据类型

转换成:

text
包装类对象

叫做:

text
装箱
Boxing

例如:

java
int number = 100;

Integer value =
        Integer.valueOf(
                number
        );

这里:

text
int
    ↓
Integer

就是装箱。

以前也可以看到:

java
Integer value =
        new Integer(100);

但这种写法已经不推荐。

包装类构造方法已经被标记为过时,更推荐:

java
Integer.valueOf(100);

或者直接使用自动装箱:

java
Integer value = 100;

2.2 拆箱 ​

把:

text
包装类对象

转换成:

text
基本数据类型

叫:

text
拆箱
Unboxing

例如:

java
Integer value =
        Integer.valueOf(100);

int number =
        value.intValue();

这里:

text
Integer
    ↓
int

就是拆箱。

其他包装类也有类似的方法:

java
longValue();

doubleValue();

floatValue();

byteValue();

shortValue();

2.3 自动装箱 ​

Java 5 开始支持:

text
自动装箱
Autoboxing

因此:

java
Integer value = 100;

编译器会自动帮助我们完成基本类型到包装类的转换。

可以粗略理解成:

java
Integer value =
        Integer.valueOf(100);

再例如:

java
List<Integer> list =
        new ArrayList<>();

list.add(100);

add() 实际需要:

java
Integer

但我们传入的是:

java
int

编译器会自动装箱。

可以简单理解:

text
int 100
    ↓
自动装箱
    ↓
Integer

2.4 自动拆箱 ​

同样,Java 也支持:

text
自动拆箱
Auto-unboxing

例如:

java
Integer value = 100;

int number = value;

可以粗略理解为:

java
int number =
        value.intValue();

再例如:

java
Integer a = 10;

Integer b = 20;

int result =
        a + b;

执行:

java
a + b

时,包装类会发生自动拆箱。

可以理解:

text
Integer a
    ↓
int


Integer b
    ↓
int


10 + 20
    ↓
30

2.5 自动拆箱的 NullPointerException ​

自动拆箱有一个非常常见的坑。

例如:

java
Integer value = null;

int number = value;

编译阶段没有问题。

但运行时会抛出:

text
NullPointerException

因为这段代码可以理解成:

java
Integer value = null;

int number =
        value.intValue();

而:

java
value

是:

text
null

相当于:

java
null.intValue();

自然会出现:

text
NullPointerException

所以在项目中:

java
Integer count =
        user.getCount();

if (count > 0) {

}

也可能出现空指针。

因为:

java
count > 0

会触发:

text
Integer
    ↓
自动拆箱
    ↓
int

如果:

java
count == null

就会报错。

更安全:

java
if (
        count != null
                && count > 0
) {

}

或者根据业务给出默认值。

所以需要记住:

包装类可以是 null,基本数据类型不可以;自动拆箱 null 会抛出 NullPointerException。

3. 包装类常用方法 ​

包装类除了保存基本类型对应的数据之外,还提供了大量实用方法。

其中最常见的是:

text
valueOf()

parseXxx()

xxxValue()

toString()

3.1 valueOf() ​

valueOf() 通常用于:

把基本类型或者字符串转换成对应的包装类对象。

例如:

java
Integer value =
        Integer.valueOf(100);

也可以:

java
Integer value =
        Integer.valueOf(
                "100"
        );

结果:

text
Integer 100

其他类型:

java
Long.valueOf("100");

Double.valueOf("99.9");

Boolean.valueOf("true");

3.2 parseXxx() ​

parseXxx() 通常用于:

把字符串解析成基本数据类型。

例如:

java
int number =
        Integer.parseInt(
                "100"
        );

这里返回的是:

text
int

而不是:

text
Integer

再例如:

java
long value =
        Long.parseLong(
                "100"
        );

double price =
        Double.parseDouble(
                "99.99"
        );

boolean enabled =
        Boolean.parseBoolean(
                "true"
        );

3.3 valueOf() 和 parseXxx() 的区别 ​

例如:

java
Integer value =
        Integer.valueOf(
                "100"
        );

返回:

text
Integer

而:

java
int value =
        Integer.parseInt(
                "100"
        );

返回:

text
int

可以直接记:

text
Integer.valueOf()
    ↓
Integer


Integer.parseInt()
    ↓
int

类似:

text
Long.valueOf()
    ↓
Long


Long.parseLong()
    ↓
long

3.4 字符串解析失败 ​

例如:

java
Integer.parseInt(
        "kerwim"
);

无法把:

text
kerwim

解析成整数。

运行时会抛出:

text
NumberFormatException

例如:

java
try {

    int value =
            Integer.parseInt(
                    "abc"
            );

} catch (
        NumberFormatException e) {

    System.out.println(
            "不是合法数字"
    );
}

所以:

text
字符串
    ↓
parseInt()
    ↓
格式正确
        ↓
    int


格式错误
    ↓
NumberFormatException

3.5 xxxValue() ​

数字包装类继承:

java
Number

因此可以使用:

java
intValue()

longValue()

floatValue()

doubleValue()

例如:

java
Integer number = 100;

long longValue =
        number.longValue();

double doubleValue =
        number.doubleValue();

结果:

text
100

100.0

3.6 toString() ​

包装类可以转换成字符串:

java
Integer value = 100;

String text =
        value.toString();

结果:

text
"100"

也可以:

java
String text =
        Integer.toString(
                100
        );

或者实际开发中经常直接:

java
String text =
        String.valueOf(
                100
        );

4. 包装类的 == 和 equals ​

包装类最容易踩坑的地方之一就是:

text
==

equals()

4.1 == 比较什么 ​

对于两个引用类型:

java
Integer a;

Integer b;

使用:

java
a == b

比较的是:

两个引用是否指向同一个对象。

例如:

java
Integer a =
        new Integer(1000);

Integer b =
        new Integer(1000);

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

结果是:

text
false

因为:

text
a
    ↓
Integer 对象 A


b
    ↓
Integer 对象 B

虽然里面保存的数字都是:

text
1000

但它们不是同一个对象。

由于包装类构造器已经不推荐使用,实际代码也不要再这样创建。

4.2 equals() 比较值 ​

如果希望比较包装类保存的:

text
数值

应该优先使用:

java
equals()

例如:

java
Integer a = 1000;

Integer b = 1000;

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

结果:

text
true

所以:

text
==
    ↓
引用是否相同


equals()
    ↓
包装值是否相等

5. 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

为什么?

5.1 IntegerCache ​

自动装箱:

java
Integer a = 127;

可以粗略理解成:

java
Integer a =
        Integer.valueOf(127);

而:

java
Integer.valueOf()

会使用:

text
IntegerCache

缓存一部分常用的整数对象。

Java 至少会缓存:

text
-128 ~ 127

因此:

java
Integer a = 127;

Integer b = 127;

实际可能:

text
a ──┐
    ├──→ 同一个 Integer(127)
b ──┘

所以:

java
a == b

得到:

text
true

而:

java
Integer a = 128;

Integer b = 128;

如果这个值不在实际缓存范围内,就可能分别得到不同对象:

text
a ──→ Integer(128)

b ──→ Integer(128)

于是:

java
a == b

为:

text
false

需要注意:

Integer 缓存至少包含 -128 ~ 127,具体 JVM 实现和配置可能缓存更大的范围,所以不要依赖缓存范围编写业务逻辑。

真正比较包装类数值应该:

java
a.equals(b)

而不是依赖:

java
==

5.2 Integer 缓存示例 ​

java
Integer a = 127;

Integer b = 127;

Integer c = 128;

Integer d = 128;

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

System.out.println(
        c == d
);

System.out.println(
        c.equals(d)
);

常见结果:

text
true
false
true

所以面试中看到:

java
Integer a = 127;
Integer b = 127;

不要简单认为:

text
包装类 == 永远 false

而应该想到:

text
自动装箱
    ↓
Integer.valueOf()
    ↓
IntegerCache

5.3 其他包装类缓存 ​

除了:

java
Integer

之外,一些其他包装类也会缓存常用值。

例如:

text
Byte

Short

Long

Character

Boolean

都有相应的缓存或固定实例机制。

例如:

java
Boolean.TRUE

Boolean.FALSE

不过实际开发中没有必要死记所有实现细节。

最重要的原则仍然是:

包装类比较值,优先使用 equals(),不要依赖 == 和缓存机制。

5.4 Float 和 Double ​

Float 和 Double 不使用类似整数:

text
-128 ~ 127

这样的常规对象缓存逻辑。

例如:

java
Double a = 1.0;

Double b = 1.0;

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

一般:

text
false

所以更不能用:

java
==

判断包装对象的数值是否相同。

6. 包装类 equals() 的注意事项 ​

包装类的:

java
equals()

通常要求:

text
类型相同

并且值相同

例如:

java
Integer a = 100;

Long b = 100L;

虽然数学上:

text
100 == 100

但:

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

结果:

text
false

因为:

text
Integer

和:

text
Long

不是同一种类型。

可以理解:

text
Integer(100)
    ≠
Long(100)

所以:

java
Integer.valueOf(100)
        .equals(
                Long.valueOf(100)
        );

结果:

text
false

6.1 包装类与基本类型使用 == ​

如果一边是包装类:

java
Integer value = 1000;

另一边是基本类型:

java
int number = 1000;

那么:

java
value == number

会触发:

text
自动拆箱

可以粗略理解:

java
value.intValue()
        == number

所以:

java
Integer value = 1000;

int number = 1000;

System.out.println(
        value == number
);

结果:

text
true

但是如果:

java
Integer value = null;

int number = 1000;

System.out.println(
        value == number
);

会因为自动拆箱抛出:

text
NullPointerException

所以分析:

java
==

时首先要看:

text
两边都是引用?

还是一边是基本类型?

7. 包装类是不可变对象 ​

Java 的包装类基本都属于:

text
不可变对象
Immutable Object

例如:

java
Integer value = 100;

value = value + 1;

看起来好像修改了:

text
value

里面的数据。

实际上过程更接近:

text
Integer(100)
    ↓
拆箱
    ↓
int 100
    ↓
100 + 1
    ↓
101
    ↓
重新装箱
    ↓
Integer(101)
    ↓
value 指向新的对象

原来的:

text
Integer(100)

并没有被修改。

因此:

text
包装类对象
    ↓
创建以后内部值不会改变

这和:

java
String

非常类似。

8. 包装类与泛型 ​

Java 泛型不能直接使用:

text
基本数据类型

例如:

java
// 编译错误
// List<int> numbers;

必须:

java
List<Integer> numbers =
        new ArrayList<>();

这时候自动装箱让代码使用起来非常自然:

java
numbers.add(1);

numbers.add(2);

numbers.add(3);

实际上:

text
int
    ↓
自动装箱
    ↓
Integer

取出:

java
int value =
        numbers.get(0);

又发生:

text
Integer
    ↓
自动拆箱
    ↓
int

所以集合代码:

java
List<Integer> numbers =
        List.of(
                1,
                2,
                3
        );

背后实际上大量依赖:

text
包装类

自动装箱

自动拆箱

9. 包装类在实际开发中的选择 ​

基本类型和包装类并不是随便选择的。

它们最大的区别之一是:

text
基本类型
    ↓
不能为 null


包装类型
    ↓
可以为 null

例如:

java
int age;

字段默认值:

text
0

而:

java
Integer age;

字段默认值:

text
null

9.1 什么时候使用基本类型 ​

如果一个值:

text
一定存在

不存在 null 语义

通常基本类型更加直接。

例如:

java
int count = 0;

boolean enabled = false;

9.2 什么时候使用包装类型 ​

如果需要表达:

text
没有值

未知

尚未设置

包装类型更加合适。

例如:

java
Integer age;

此时:

text
null

可以表示:

text
用户还没有填写年龄

而:

java
0

可能又是一种真正的数据。

再比如数据库字段:

text
age = NULL

映射到 Java 时:

java
Integer age;

比:

java
int age;

更容易准确表达:

text
NULL

的语义。

9.3 DTO / Entity 中为什么常见包装类 ​

实际后端项目中经常看到:

java
public class UserDTO {

    private Long id;

    private Integer age;

    private Boolean enabled;
}

而不是:

java
private long id;

private int age;

private boolean enabled;

一个很重要的原因就是:

text
包装类可以表示 null

例如更新接口:

json
{
  "name": "kerwim"
}

如果:

java
Integer age;

是:

text
null

可以理解:

text
客户端没有传 age

但如果使用:

java
int age;

默认就是:

text
0

程序可能无法区分:

text
客户端没有传

和:

text
客户端明确传了 0

所以包装类在:

text
DTO

Entity

数据库映射

接口参数

中非常常见。

10. 包装类常见坑 ​

10.1 null 自动拆箱 ​

java
Integer value = null;

int number = value;

结果:

text
NullPointerException

10.2 使用 == 比较包装对象 ​

java
Integer a = 1000;

Integer b = 1000;

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

不能依赖:

text
==

判断数值。

应该:

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

如果对象可能为:

text
null

可以使用:

java
Objects.equals(
        a,
        b
);

例如:

java
Integer a = null;

Integer b = null;

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

结果:

text
true

而且不会:

text
NullPointerException

10.3 Integer 和 Long 的 equals() ​

java
Integer a = 1;

Long b = 1L;

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

结果:

text
false

因为包装类:

text
类型不同

即使数值看起来一样,也不会认为:

text
equals

相等。

10.4 parseInt() 可能抛异常 ​

java
Integer.parseInt(
        "Java"
);

会抛出:

text
NumberFormatException

所以处理外部输入时,需要考虑:

text
字符串是不是合法数字

10.5 不要使用 new Integer() ​

不要:

java
Integer value =
        new Integer(100);

更推荐:

java
Integer value =
        Integer.valueOf(100);

或者:

java
Integer value = 100;

原因:

text
new
    ↓
一定创建新的包装对象

而:

text
valueOf()
    ↓
可以利用缓存

并且包装类构造方法本身已经不推荐继续使用。

11. 小结 ​

Java 的 8 种基本数据类型都有对应包装类:

text
byte
    ↓
Byte


short
    ↓
Short


int
    ↓
Integer


long
    ↓
Long


float
    ↓
Float


double
    ↓
Double


char
    ↓
Character


boolean
    ↓
Boolean

基本类型转包装类:

text
装箱
Boxing

包装类转基本类型:

text
拆箱
Unboxing

Java 5 以后支持:

text
自动装箱

自动拆箱

例如:

java
Integer value = 100;

int number = value;

但需要特别注意:

text
Integer value = null
    ↓
自动拆箱
    ↓
NullPointerException

常见转换:

text
Integer.valueOf("100")
    ↓
Integer


Integer.parseInt("100")
    ↓
int


integer.intValue()
    ↓
int

包装类比较:

text
==
    ↓
引用比较
    ↓
还可能受到自动拆箱和缓存影响


equals()
    ↓
比较同类型包装对象的值

Integer 缓存:

text
Integer.valueOf()
    ↓
至少缓存 -128 ~ 127

所以:

java
Integer a = 127;

Integer b = 127;

a == b;

可能看到:

text
true

但绝对不要因此形成:

text
包装类可以用 == 比较值

这种结论。

实际开发中比较包装类:

java
Objects.equals(
        a,
        b
);

通常更加稳妥。

包装类与泛型:

text
List<int>
    ✗


List<Integer>
    ✓

包装类和基本类型最大的业务区别之一:

text
基本类型
    ↓
不能表示 null


包装类
    ↓
可以表示 null
    ↓
可以表达“未设置 / 未传值 / 未知”

一句话概括:

包装类就是基本数据类型在 Java 对象体系中的对应形式,它让基本类型能够参与泛型、集合和对象化 API,同时也带来了自动装箱、拆箱、缓存以及 null 等需要注意的问题。

Noctiflorous Garden · MIT License