包装类
字数: 0 字 时长: 0 分钟
Java 包装类
字数: 0 字 时长: 0 分钟
1. 包装类概述
Java 中存在:
基本数据类型
Primitive Type例如:
int age = 18;
double price = 99.9;
boolean enabled = true;但基本数据类型本身并不是对象。
为了让基本数据类型也能够以:
对象的形式参与 Java 的面向对象体系,Java 为每一种基本数据类型提供了对应的:
包装类
Wrapper Class例如:
int
↓
Integer于是:
int age = 18;也可以表示为:
Integer age = 18;可以简单理解:
基本数据类型
↓
包装成对象
↓
包装类1.1 基本数据类型与包装类
Java 的 8 种基本数据类型都有对应的包装类:
| 基本数据类型 | 包装类 |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
其中需要特别注意:
int
↓
Integer
char
↓
Character并不是:
Int
Char另外 Java 还有:
Void这个特殊类,用于表示:
void相关的类型语义。
但它和其他包装类不太一样,平时基本不会直接创建或使用:
Void对象。
1.2 为什么需要包装类
既然已经有:
int
double
boolean为什么还需要:
Integer
Double
Boolean?
主要原因是:
Java 中很多 API 和面向对象机制只能操作对象,而不能直接操作基本数据类型。
例如泛型:
List<Integer> list =
new ArrayList<>();不能写:
// 编译错误
// List<int> list =
// new ArrayList<>();因为 Java 泛型要求类型参数是:
引用类型而:
int属于基本数据类型。
所以需要:
int
↓
Integer再例如:
Map<Long, User>这里:
Long也是:
long对应的包装类。
包装类还提供了很多基本类型本身没有的方法:
Integer.parseInt()
Integer.valueOf()
Integer.compare()
Integer.max()
Double.parseDouble()
Boolean.parseBoolean()所以包装类不仅仅是:
给基本类型套一层对象它还提供了:
类型转换
解析
比较
常量
工具方法等能力。
1.3 包装类的继承关系
大部分数字包装类继承:
java.lang.Number关系可以简单理解:
Object
├── Number
│ ├── Byte
│ ├── Short
│ ├── Integer
│ ├── Long
│ ├── Float
│ └── Double
│
├── Character
└── BooleanNumber 是一个抽象类。
它定义了一些将数字转换为基本类型的方法:
intValue()
longValue()
floatValue()
doubleValue()例如:
Integer value = 100;
double number =
value.doubleValue();结果:
100.02. 装箱与拆箱
基本数据类型和包装类之间经常需要进行转换。
例如:
int
↕
Integer这个过程分别叫:
装箱
Boxing
拆箱
Unboxing2.1 装箱
把:
基本数据类型转换成:
包装类对象叫做:
装箱
Boxing例如:
int number = 100;
Integer value =
Integer.valueOf(
number
);这里:
int
↓
Integer就是装箱。
以前也可以看到:
Integer value =
new Integer(100);但这种写法已经不推荐。
包装类构造方法已经被标记为过时,更推荐:
Integer.valueOf(100);或者直接使用自动装箱:
Integer value = 100;2.2 拆箱
把:
包装类对象转换成:
基本数据类型叫:
拆箱
Unboxing例如:
Integer value =
Integer.valueOf(100);
int number =
value.intValue();这里:
Integer
↓
int就是拆箱。
其他包装类也有类似的方法:
longValue();
doubleValue();
floatValue();
byteValue();
shortValue();2.3 自动装箱
Java 5 开始支持:
自动装箱
Autoboxing因此:
Integer value = 100;编译器会自动帮助我们完成基本类型到包装类的转换。
可以粗略理解成:
Integer value =
Integer.valueOf(100);再例如:
List<Integer> list =
new ArrayList<>();
list.add(100);add() 实际需要:
Integer但我们传入的是:
int编译器会自动装箱。
可以简单理解:
int 100
↓
自动装箱
↓
Integer2.4 自动拆箱
同样,Java 也支持:
自动拆箱
Auto-unboxing例如:
Integer value = 100;
int number = value;可以粗略理解为:
int number =
value.intValue();再例如:
Integer a = 10;
Integer b = 20;
int result =
a + b;执行:
a + b时,包装类会发生自动拆箱。
可以理解:
Integer a
↓
int
Integer b
↓
int
10 + 20
↓
302.5 自动拆箱的 NullPointerException
自动拆箱有一个非常常见的坑。
例如:
Integer value = null;
int number = value;编译阶段没有问题。
但运行时会抛出:
NullPointerException因为这段代码可以理解成:
Integer value = null;
int number =
value.intValue();而:
value是:
null相当于:
null.intValue();自然会出现:
NullPointerException所以在项目中:
Integer count =
user.getCount();
if (count > 0) {
}也可能出现空指针。
因为:
count > 0会触发:
Integer
↓
自动拆箱
↓
int如果:
count == null就会报错。
更安全:
if (
count != null
&& count > 0
) {
}或者根据业务给出默认值。
所以需要记住:
包装类可以是 null,基本数据类型不可以;自动拆箱 null 会抛出 NullPointerException。
3. 包装类常用方法
包装类除了保存基本类型对应的数据之外,还提供了大量实用方法。
其中最常见的是:
valueOf()
parseXxx()
xxxValue()
toString()3.1 valueOf()
valueOf() 通常用于:
把基本类型或者字符串转换成对应的包装类对象。
例如:
Integer value =
Integer.valueOf(100);也可以:
Integer value =
Integer.valueOf(
"100"
);结果:
Integer 100其他类型:
Long.valueOf("100");
Double.valueOf("99.9");
Boolean.valueOf("true");3.2 parseXxx()
parseXxx() 通常用于:
把字符串解析成基本数据类型。
例如:
int number =
Integer.parseInt(
"100"
);这里返回的是:
int而不是:
Integer再例如:
long value =
Long.parseLong(
"100"
);
double price =
Double.parseDouble(
"99.99"
);
boolean enabled =
Boolean.parseBoolean(
"true"
);3.3 valueOf() 和 parseXxx() 的区别
例如:
Integer value =
Integer.valueOf(
"100"
);返回:
Integer而:
int value =
Integer.parseInt(
"100"
);返回:
int可以直接记:
Integer.valueOf()
↓
Integer
Integer.parseInt()
↓
int类似:
Long.valueOf()
↓
Long
Long.parseLong()
↓
long3.4 字符串解析失败
例如:
Integer.parseInt(
"kerwim"
);无法把:
kerwim解析成整数。
运行时会抛出:
NumberFormatException例如:
try {
int value =
Integer.parseInt(
"abc"
);
} catch (
NumberFormatException e) {
System.out.println(
"不是合法数字"
);
}所以:
字符串
↓
parseInt()
↓
格式正确
↓
int
格式错误
↓
NumberFormatException3.5 xxxValue()
数字包装类继承:
Number因此可以使用:
intValue()
longValue()
floatValue()
doubleValue()例如:
Integer number = 100;
long longValue =
number.longValue();
double doubleValue =
number.doubleValue();结果:
100
100.03.6 toString()
包装类可以转换成字符串:
Integer value = 100;
String text =
value.toString();结果:
"100"也可以:
String text =
Integer.toString(
100
);或者实际开发中经常直接:
String text =
String.valueOf(
100
);4. 包装类的 == 和 equals
包装类最容易踩坑的地方之一就是:
==
equals()4.1 == 比较什么
对于两个引用类型:
Integer a;
Integer b;使用:
a == b比较的是:
两个引用是否指向同一个对象。
例如:
Integer a =
new Integer(1000);
Integer b =
new Integer(1000);
System.out.println(
a == b
);结果是:
false因为:
a
↓
Integer 对象 A
b
↓
Integer 对象 B虽然里面保存的数字都是:
1000但它们不是同一个对象。
由于包装类构造器已经不推荐使用,实际代码也不要再这样创建。
4.2 equals() 比较值
如果希望比较包装类保存的:
数值应该优先使用:
equals()例如:
Integer a = 1000;
Integer b = 1000;
System.out.println(
a.equals(b)
);结果:
true所以:
==
↓
引用是否相同
equals()
↓
包装值是否相等5. Integer 缓存
下面这段代码非常经典:
Integer a = 127;
Integer b = 127;
System.out.println(
a == b
);结果:
true但是:
Integer a = 128;
Integer b = 128;
System.out.println(
a == b
);通常结果:
false为什么?
5.1 IntegerCache
自动装箱:
Integer a = 127;可以粗略理解成:
Integer a =
Integer.valueOf(127);而:
Integer.valueOf()会使用:
IntegerCache缓存一部分常用的整数对象。
Java 至少会缓存:
-128 ~ 127因此:
Integer a = 127;
Integer b = 127;实际可能:
a ──┐
├──→ 同一个 Integer(127)
b ──┘所以:
a == b得到:
true而:
Integer a = 128;
Integer b = 128;如果这个值不在实际缓存范围内,就可能分别得到不同对象:
a ──→ Integer(128)
b ──→ Integer(128)于是:
a == b为:
false需要注意:
Integer 缓存至少包含
-128 ~ 127,具体 JVM 实现和配置可能缓存更大的范围,所以不要依赖缓存范围编写业务逻辑。
真正比较包装类数值应该:
a.equals(b)而不是依赖:
==5.2 Integer 缓存示例
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)
);常见结果:
true
false
true所以面试中看到:
Integer a = 127;
Integer b = 127;不要简单认为:
包装类 == 永远 false而应该想到:
自动装箱
↓
Integer.valueOf()
↓
IntegerCache5.3 其他包装类缓存
除了:
Integer之外,一些其他包装类也会缓存常用值。
例如:
Byte
Short
Long
Character
Boolean都有相应的缓存或固定实例机制。
例如:
Boolean.TRUE
Boolean.FALSE不过实际开发中没有必要死记所有实现细节。
最重要的原则仍然是:
包装类比较值,优先使用 equals(),不要依赖 == 和缓存机制。
5.4 Float 和 Double
Float 和 Double 不使用类似整数:
-128 ~ 127这样的常规对象缓存逻辑。
例如:
Double a = 1.0;
Double b = 1.0;
System.out.println(
a == b
);一般:
false所以更不能用:
==判断包装对象的数值是否相同。
6. 包装类 equals() 的注意事项
包装类的:
equals()通常要求:
类型相同
并且值相同例如:
Integer a = 100;
Long b = 100L;虽然数学上:
100 == 100但:
System.out.println(
a.equals(b)
);结果:
false因为:
Integer和:
Long不是同一种类型。
可以理解:
Integer(100)
≠
Long(100)所以:
Integer.valueOf(100)
.equals(
Long.valueOf(100)
);结果:
false6.1 包装类与基本类型使用 ==
如果一边是包装类:
Integer value = 1000;另一边是基本类型:
int number = 1000;那么:
value == number会触发:
自动拆箱可以粗略理解:
value.intValue()
== number所以:
Integer value = 1000;
int number = 1000;
System.out.println(
value == number
);结果:
true但是如果:
Integer value = null;
int number = 1000;
System.out.println(
value == number
);会因为自动拆箱抛出:
NullPointerException所以分析:
==时首先要看:
两边都是引用?
还是一边是基本类型?7. 包装类是不可变对象
Java 的包装类基本都属于:
不可变对象
Immutable Object例如:
Integer value = 100;
value = value + 1;看起来好像修改了:
value里面的数据。
实际上过程更接近:
Integer(100)
↓
拆箱
↓
int 100
↓
100 + 1
↓
101
↓
重新装箱
↓
Integer(101)
↓
value 指向新的对象原来的:
Integer(100)并没有被修改。
因此:
包装类对象
↓
创建以后内部值不会改变这和:
String非常类似。
8. 包装类与泛型
Java 泛型不能直接使用:
基本数据类型例如:
// 编译错误
// List<int> numbers;必须:
List<Integer> numbers =
new ArrayList<>();这时候自动装箱让代码使用起来非常自然:
numbers.add(1);
numbers.add(2);
numbers.add(3);实际上:
int
↓
自动装箱
↓
Integer取出:
int value =
numbers.get(0);又发生:
Integer
↓
自动拆箱
↓
int所以集合代码:
List<Integer> numbers =
List.of(
1,
2,
3
);背后实际上大量依赖:
包装类
自动装箱
自动拆箱9. 包装类在实际开发中的选择
基本类型和包装类并不是随便选择的。
它们最大的区别之一是:
基本类型
↓
不能为 null
包装类型
↓
可以为 null例如:
int age;字段默认值:
0而:
Integer age;字段默认值:
null9.1 什么时候使用基本类型
如果一个值:
一定存在
不存在 null 语义通常基本类型更加直接。
例如:
int count = 0;
boolean enabled = false;9.2 什么时候使用包装类型
如果需要表达:
没有值
未知
尚未设置包装类型更加合适。
例如:
Integer age;此时:
null可以表示:
用户还没有填写年龄而:
0可能又是一种真正的数据。
再比如数据库字段:
age = NULL映射到 Java 时:
Integer age;比:
int age;更容易准确表达:
NULL的语义。
9.3 DTO / Entity 中为什么常见包装类
实际后端项目中经常看到:
public class UserDTO {
private Long id;
private Integer age;
private Boolean enabled;
}而不是:
private long id;
private int age;
private boolean enabled;一个很重要的原因就是:
包装类可以表示 null例如更新接口:
{
"name": "kerwim"
}如果:
Integer age;是:
null可以理解:
客户端没有传 age但如果使用:
int age;默认就是:
0程序可能无法区分:
客户端没有传和:
客户端明确传了 0所以包装类在:
DTO
Entity
数据库映射
接口参数中非常常见。
10. 包装类常见坑
10.1 null 自动拆箱
Integer value = null;
int number = value;结果:
NullPointerException10.2 使用 == 比较包装对象
Integer a = 1000;
Integer b = 1000;
System.out.println(
a == b
);不能依赖:
==判断数值。
应该:
System.out.println(
a.equals(b)
);如果对象可能为:
null可以使用:
Objects.equals(
a,
b
);例如:
Integer a = null;
Integer b = null;
System.out.println(
Objects.equals(
a,
b
)
);结果:
true而且不会:
NullPointerException10.3 Integer 和 Long 的 equals()
Integer a = 1;
Long b = 1L;
System.out.println(
a.equals(b)
);结果:
false因为包装类:
类型不同即使数值看起来一样,也不会认为:
equals相等。
10.4 parseInt() 可能抛异常
Integer.parseInt(
"Java"
);会抛出:
NumberFormatException所以处理外部输入时,需要考虑:
字符串是不是合法数字10.5 不要使用 new Integer()
不要:
Integer value =
new Integer(100);更推荐:
Integer value =
Integer.valueOf(100);或者:
Integer value = 100;原因:
new
↓
一定创建新的包装对象而:
valueOf()
↓
可以利用缓存并且包装类构造方法本身已经不推荐继续使用。
11. 小结
Java 的 8 种基本数据类型都有对应包装类:
byte
↓
Byte
short
↓
Short
int
↓
Integer
long
↓
Long
float
↓
Float
double
↓
Double
char
↓
Character
boolean
↓
Boolean基本类型转包装类:
装箱
Boxing包装类转基本类型:
拆箱
UnboxingJava 5 以后支持:
自动装箱
自动拆箱例如:
Integer value = 100;
int number = value;但需要特别注意:
Integer value = null
↓
自动拆箱
↓
NullPointerException常见转换:
Integer.valueOf("100")
↓
Integer
Integer.parseInt("100")
↓
int
integer.intValue()
↓
int包装类比较:
==
↓
引用比较
↓
还可能受到自动拆箱和缓存影响
equals()
↓
比较同类型包装对象的值Integer 缓存:
Integer.valueOf()
↓
至少缓存 -128 ~ 127所以:
Integer a = 127;
Integer b = 127;
a == b;可能看到:
true但绝对不要因此形成:
包装类可以用 == 比较值这种结论。
实际开发中比较包装类:
Objects.equals(
a,
b
);通常更加稳妥。
包装类与泛型:
List<int>
✗
List<Integer>
✓包装类和基本类型最大的业务区别之一:
基本类型
↓
不能表示 null
包装类
↓
可以表示 null
↓
可以表达“未设置 / 未传值 / 未知”一句话概括:
包装类就是基本数据类型在 Java 对象体系中的对应形式,它让基本类型能够参与泛型、集合和对象化 API,同时也带来了自动装箱、拆箱、缓存以及 null 等需要注意的问题。