1. 核心概念:什么是包装类型?
包装类型 是一种特殊的类,它将基本数据类型(primitive type)"包装"起来,使其成为一个对象。
简单来说,它就是基本数据类型的对象形式。
基本数据类型 | 对应的包装类型(java.lang包下) |
---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
2. 为什么需要包装类型?(存在的理由)
这是最关键的问题。既然已经有了基本类型,为什么还要多此一举发明包装类型?主要有以下三个核心原因:
原因一:让基本类型能用于"面向对象"的场景
Java是一个面向对象的语言,但八大基本数据类型(如 int
, char
)不是对象,它们不具备对象的特性(比如不能调用方法,没有继承关系)。
而很多Java的高级特性和API是专门为对象设计的 。最典型的例子就是我们刚才讨论的集合框架。
-
问题 :
ArrayList<int>
这样的写法是错误 的!因为泛型<T>
中的T
必须是类类型(引用类型),而不能是基本类型。 -
解决方案 :使用
ArrayList<Integer>
。Integer
是int
的包装类,它是一个真正的类,所以可以用于泛型。
java
// 错误!不能将基本类型用于泛型
// ArrayList<int> list = new ArrayList<>();
// 正确!使用包装类型
ArrayList<Integer> list = new ArrayList<>();
list.add(10); // 10 被自动转换为 Integer 对象
原因二:为基本类型提供丰富的工具方法
包装类提供了一系列有用的静态方法和常量,方便我们对基本类型数据进行操作。
例如,Integer
类:
-
静态方法:
-
Integer.parseInt("123")
:将字符串转换为int
。 -
Integer.valueOf(123)
:将int
转换为Integer
对象。 -
Integer.max(a, b)
:比较两个数的大小。
-
-
常用常量:
-
Integer.MAX_VALUE
:int
的最大值 (2^31 - 1)。 -
Integer.MIN_VALUE
:int
的最小值 (-2^31)。
-
java
String numStr = "456";
int num = Integer.parseInt(numStr); // 字符串 -> 基本类型
Integer numObj = Integer.valueOf(numStr); // 字符串 -> 包装对象
System.out.println(Integer.MAX_VALUE); // 输出: 2147483647
原因三:可以表示null
(空值)
基本类型变量一定有值(比如 int
默认是0),而包装类型是引用类型,其变量可以为 null
。这在某些场景下非常有用。
- 场景 :从数据库查询一个"年龄"字段,如果这个字段的值未知,在数据库中可能是
NULL
。如果用int
接收,你无法区分是"0岁"还是"未知";如果用Integer
接收,null
就可以明确表示"未知"。
java
// 从数据库获取数据
int agePrimitive = 0; // 无法区分是0岁还是数据不存在
Integer ageWrapper = null; // 明确表示数据不存在
3. 自动装箱与自动拆箱
从Java 5开始,引入了自动装箱 和自动拆箱机制,极大地简化了包装类型和基本类型之间的转换。
-
自动装箱 :编译器自动将基本类型 转换为对应的包装类型。
Integer i = 10;
等价于Integer i = Integer.valueOf(10);
-
自动拆箱 :编译器自动将包装类型 转换为对应的基本类型。
int j = i;
等价于int j = i.intValue();
java
// 自动装箱示例
ArrayList<Integer> list = new ArrayList<>();
list.add(100); // 自动装箱:编译器将 int 100 转换为 Integer.valueOf(100)
// 自动拆箱示例
int firstElement = list.get(0); // 自动拆箱:编译器将 Integer 转换为 intValue()
Integer a = 500;
int b = a + 1; // 先对 a 自动拆箱,完成计算 a.intValue() + 1
正是因为自动装箱/拆箱的存在,我们在使用集合框架时,才能像操作基本类型一样自然地操作包装类型。
4. 注意事项与陷阱(面试常考)
陷阱一:==
和 equals()
的区别
这是一个经典的坑!==
比较的是对象的引用(内存地址) ,而 equals()
比较的是对象的值。
-
对于
-128 到 127
之间的整数 :Java为了优化内存,对这个范围内的Integer
对象进行了缓存。通过自动装箱或valueOf()
方法创建时,会直接返回缓存中的对象。所以用==
比较可能会返回true
。 -
对于超出此范围的整数 :每次都会创建新的对象,
==
比较就会返回false
。
java
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true,因为从缓存中取,是同一个对象
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false,因为超出了缓存范围,是新创建的对象
// 正确的比较方式:始终使用 .equals() 来比较值!
System.out.println(a.equals(b)); // true
System.out.println(c.equals(d)); // true
最佳实践:比较包装类型的值,一律使用 equals()
方法!
陷阱二:空指针异常
自动拆箱时,如果包装类对象是 null
,则会抛出 NullPointerException
。
java
Integer possibleNull = null;
// 以下代码在运行时都会抛出 NullPointerException
// int num = possibleNull; // 自动拆箱调用 possibleNull.intValue()
// System.out.println(possibleNull + 1);
陷阱三:性能开销
虽然自动装箱/拆箱很方便,但它是有性能成本的,因为背后涉及对象的创建和方法调用。在需要高性能计算的大循环中,应优先使用基本类型。
java
// 性能较差
Long sum = 0L; // 包装类型,每次循环都会发生自动装箱
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // 相当于 sum = Long.valueOf(sum.longValue() + i);
}
// 性能更好
long sumFast = 0L; // 基本类型,无额外开销
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sumFast += i;
}