包装类
- 什么是包装类?
- 为什么需要包装类?
-
-
- [1. 泛型与集合的必需品](#1. 泛型与集合的必需品)
- [2. 对象方法的调用](#2. 对象方法的调用)
- 3.实体类(POJO)中使用包装类
-
- 装箱与拆箱⭐
-
- [手动 装箱/拆箱](#手动 装箱/拆箱)
- [自动 装箱/拆箱](#自动 装箱/拆箱)
- [valueOf() 源码分析(包装类缓存池) ⭐](#valueOf() 源码分析(包装类缓存池) ⭐)
- 包装类在集合中的应用与性能考量
-
- [开销分析 ⭐](#开销分析 ⭐)
- 性能对比测试
- [null 拆箱引发的 NPE](#null 拆箱引发的 NPE)
- 包装类与基本类型的比较
在Java中,有一句广为流传的名言:"万物皆对象 "。然而,这句话其实并不完全准确。因为我们还有8个"异类"------int、double、boolean等基本数据类型。它们不是对象,无法调用方法,也无法在集合中存储。
为了解决这个问题,包装类应运而生。它们就像给基本数据类型穿上了一件"对象的外衣"。
什么是包装类?
包装类是Java为每种基本数据类型提供的对应的引用类型。
| 基本类型 | 包装类 |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer⭐ |
long |
Long |
float |
Float |
double |
Double |
char |
Character ⭐ |
boolean |
Boolean |
注意 :除了
Integer和Character,其他包装类的名字与基本类型名首字母大写即可。
为什么需要包装类?
1. 泛型与集合的必需品
Java的泛型不支持基本类型。如果想创建一个ArrayList来存储整数,必须使用ArrayList<Integer>。如果试图写ArrayList<int>,编译器会直接报错。
2. 对象方法的调用
包装类提供了许多实用的静态方法,方便类型转换。
java
// 将字符串转换为整数
int num = Integer.parseInt("123");
// 将整数转换为二进制字符串
String binary = Integer.toBinaryString(10); // 输出 "1010"
3.实体类(POJO)中使用包装类
在领域模型中,建议使用包装类。
- 数据库的字段可能是
NULL,映射到int会变成0,产生业务歧义。 - 使用
Integer可以完美区分"未设置"和"设置为0"。
装箱与拆箱⭐
- 装箱:将基本类型转换为包装类对象。
- 拆箱:将包装类对象转换为基本类型。
手动 装箱/拆箱
java
int a = 10;
Integer num =Integer.valueOf(a); // 手动装箱
int b = num.intValue(); // 手动拆箱
double c=num.doubleValue();
自动 装箱/拆箱
java
Integer num = 10; // 自动装箱:相当于 Integer.valueOf(10)
int d = num; // 自动拆箱:相当于 num.intValue()
valueOf() 源码分析(包装类缓存池) ⭐
这是包装类中最容易出错,也是面试中最常考的点:缓存池 。
我们来看一段代码:
java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出 true 还是 false?
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // 输出 true 还是 false?
运行结果:
true
false
为什么呢?ctrl+B看看valueOf() 源码
cpp
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
使用自动装箱(Integer.valueOf())时,JVM为了提高性能和节省内存,会对特定范围内的数值进行缓存。
IntegerCache.low是static final int low = -128;IntegerCache.high是static final int high= 127- 代码逻辑 :
- 如果
i的值在[-128, 127]之间,valueOf()方法会直接从缓存数组中返回同一个Integer对象。 - 如果超出这个范围,则会
new一个新的Integer对象。
- 如果
在例子中,a和b都指向缓存中的同一个对象,所以==比较内存地址返回true。而c和d是两个不同的对象,返回false。
注意 :
==比较的是内存地址(引用),比较数值请使用equals()方法。
更多补充 :缓存池和常量池的区别
包装类在集合中的应用与性能考量
开销分析 ⭐
虽然包装类很方便,但在高频数据计算中,它带来了额外的性能开销。
java
// 性能较差的写法
Integer sum = 0; // Integer sum = Integer.valueOf(0);
for (int i = 0; i < 10000; i++) {
sum += i;
// sum = Integer.valueOf(sum.intValue() + i);
// 这里每次循环都发生了:拆箱 -> 计算 -> 装箱
}
10000次循环会创建约10000个Integer对象(实际上因为缓存机制,0-127范围内的值可能复用,但整体数量依然很大)。这会增加GC(垃圾回收)的压力。在高性能场景下,优先使用基本类型int进行计算。
性能对比测试
java
public class PerformanceTest {
// 使用包装类
public static void testWrapper() {
long start = System.nanoTime();
Integer sum = 0;
for (int i = 0; i < 10000000; i++) { // 1000万次
sum += i;
}
long end = System.nanoTime();
System.out.println("包装类耗时: " + (end - start) / 1000000 + " ms");
}
// 使用基本类型
public static void testPrimitive() {
long start = System.nanoTime();
int sum = 0;
for (int i = 0; i < 10000000; i++) {
sum += i;
}
long end = System.nanoTime();
System.out.println("基本类型耗时: " + (end - start) / 1000000 + " ms");
}
public static void main(String[] args) {
testPrimitive(); // 输出: 基本类型耗时: 5 ms
testWrapper(); // 输出: 包装类耗时: 43 ms
}
}
null 拆箱引发的 NPE
自动拆箱时,如果包装类对象为null,会抛出NullPointerException。
java
Integer num = null;
int i = num; // 报错! 相当于执行了 num.intValue()
建议修改为:
java
Integer x = null;
int y = (x == null) ? 0 : x;
包装类与基本类型的比较
当包装类与基本类型使用==比较时,包装类会自动拆箱为基本类型,此时比较的是数值,而不是地址。
java
int a = 10;
Integer i = new Integer(100);
int j = 100;
System.out.println(i == j); // 输出 true (因为i自动拆箱了)
- 装箱/拆箱是基本类型 与包装类之间的转换。
- JDK 1.5+ 的自动装箱/拆箱是编译器语法糖 ,本质是调用
valueOf()和xxxValue()。- 常见坑:
Integer的==(缓存范围导致结果"看起来不一致")以及null拆箱导致 NPE。