包装类基础
凌晨 2 点,你在线上日志里看到一行诡异的报错:
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value is null明明 Integer 不是已经赋值了吗?怎么会 NPE?
这不是代码写错了,是你踩到了 Java 包装类的第一个坑:基本类型和对象是两套不同的逻辑。
为什么需要包装类?
Java 的基本类型(int、boolean)是「裸奔」的值,存在于栈内存,操作快但没有「身份」。
而有些场景必须用对象:
- 集合框架(
List<Integer>、Map<String, Object>)只能存对象 - 泛型参数不能是基本类型
- 需要调用方法(如
toString()、compareTo())
所以 Java 提供了 8 个包装类,把基本类型「包装」成对象:
| 基本类型 | 包装类 | 你需要知道的事 |
|---|---|---|
byte | Byte | 1 字节,-128 ~ 127 |
short | Short | 2 字节,-32768 ~ 32767 |
int | Integer | 4 字节,21 亿多 |
long | Long | 8 字节 |
float | Float | 4 字节,单精度 |
double | Double | 8 字节,双精度 |
char | Character | 2 字节,Unicode |
boolean | Boolean | 逻辑值,只有两个实例 |
继承结构:Number 和它的孩子们
六个数值型包装类(Byte、Short、Integer、Long、Float、Double)都继承自 Number 抽象类,这意味着它们共享一套数值转换方法:
java
Integer i = 100;
// 统一的方法签名,Number 定义的
i.byteValue(); // 转 byte: 100
i.shortValue(); // 转 short: 100
i.intValue(); // 转 int: 100
i.longValue(); // 转 long: 100L
i.floatValue(); // 转 float: 100.0f
i.doubleValue(); // 转 double: 100.0而 Boolean 和 Character 是两个「不听话」的孩子,没有继承 Number,各自为政。
创建包装类的正确姿势
方式一:valueOf() —— 官方推荐的正经玩法
java
// 数字类型
Integer i1 = Integer.valueOf(10); // int → Integer
Integer i2 = Integer.valueOf("10"); // String → Integer
Integer i3 = Integer.valueOf("1010", 2); // 进制转换:二进制 "1010" = 10
Long l = Long.valueOf("255", 16); // 十六进制 "FF" = 255
Boolean b = Boolean.valueOf("true"); // true(不区分大小写)
Boolean b2 = Boolean.valueOf("yes"); // false(只有 "true" 才返回 true)方式二:自动装箱(JDK 5+)—— 偷懒神器
java
Integer i = 10; // 编译器自动转为 Integer.valueOf(10)
Boolean b = true; // 编译器自动转为 Boolean.valueOf(true)看起来写起来爽了,但隐藏的坑在后面。
方式三:parseXxx() —— 我要的是基本类型
java
int n = Integer.parseInt("123"); // String → int
double d = Double.parseDouble("3.14"); // String → double
boolean b = Boolean.parseBoolean("true"); // String → boolean(同样不区分大小写)关键区别:
parseInt()返回int(基本类型)valueOf()返回Integer(包装类)
常用常量:它们是静默的守卫
java
public class WrapperConstants {
public static void main(String[] args) {
// Integer 的边界
System.out.println("int 最小值: " + Integer.MIN_VALUE); // -2147483648
System.out.println("int 最大值: " + Integer.MAX_VALUE); // 2147483647
System.out.println("int 位数: " + Integer.SIZE); // 32
System.out.println("int 字节: " + Integer.BYTES); // 4
// Long 的边界
System.out.println("long 最小值: " + Long.MIN_VALUE);
System.out.println("long 最大值: " + Long.MAX_VALUE);
// Double 的特殊值
System.out.println("最大正数: " + Double.MAX_VALUE); // 1.7976931348623157E308
System.out.println("正无穷: " + Double.POSITIVE_INFINITY);
System.out.println("负无穷: " + Double.NEGATIVE_INFINITY);
System.out.println("NaN: " + Double.NaN); // Not a Number
// Boolean 的两个单例
System.out.println(Boolean.TRUE == Boolean.valueOf(true)); // true
System.out.println(Boolean.FALSE == Boolean.valueOf(false)); // true
}
}这些常量在边界值判断、序列化、算法实现中经常用到。
装箱与拆箱的底层
你以为 Integer i = 10; 很简洁?编译器帮你做了这些:
java
// 你写的代码
Integer i = 10;
// 编译器帮你翻译成
Integer i = Integer.valueOf(10);
// 当你这样用时
int n = i;
// 编译器翻译成
int n = i.intValue();装箱:调用 valueOf()拆箱:调用 xxxValue()
这不是魔法,是语法糖。但语法糖吃多了,迟早要还的。
缓存机制:Java 的小心思
看这段代码,你猜结果是什么?
java
public class CacheDemo {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println("a == b: " + (a == b)); // ?
Integer c = 128;
Integer d = 128;
System.out.println("c == d: " + (c == d)); // ?
}
}答案是:true 和 false。
原因:Integer.valueOf() 对 -128 到 127 之间的值做了缓存,复用已有对象。
java
// Integer.valueOf() 源码简化版
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}缓存范围:
| 包装类 | 缓存范围 |
|---|---|
| Byte | 全部 -128 ~ 127 |
| Short | -128 ~ 127 |
| Integer | -128 ~ 127 |
| Long | -128 ~ 127 |
| Character | 0 ~ 127 |
| Float | 无缓存 |
| Double | 无缓存 |
| Boolean | true, false 两个实例 |
所以:
Integer a = 127; Integer b = 127;→a == b是true(同一个对象)Integer c = 128; Integer d = 128;→c == d是false(两个对象)Double d1 = 0.1; Double d2 = 0.1;→d1 == d2是false(没有缓存)
最重要的提醒
1. 废弃的构造函数,别用了
java
// 这些构造函数早已废弃,JDK 源码里都打上了 @Deprecated
Integer i = new Integer(10); // ❌ 别用
Boolean b = new Boolean("true"); // ❌ 别用
Integer i = Integer.valueOf(10); // ✅ 推荐
Boolean b = Boolean.valueOf(true); // ✅ 推荐2. 拆箱的 NPE 陷阱
java
Integer nullable = null;
// 自动拆箱 → NPE
int value = nullable; // java.lang.NullPointerException
// ✅ 先检查
int value = nullable != null ? nullable : 0;这就是开头那个报错的原因:包装类可以为 null,拆箱操作不会帮你检查。
3. 自动装箱有性能代价
java
// 循环中频繁装箱,JVM 要分配大量短期对象
Long sum = 0L; // 注意是 Long,不是 long
for (int i = 0; i < 1000000; i++) {
sum += i; // 每次 i 都要装箱成 Long,然后拆箱相加,再装箱
}
// 这段代码在堆上创建了百万个 Long 对象,GC 压力巨大
// ✅ 用基本类型
long sum = 0L;
for (int i = 0; i < 1000000; i++) {
sum += i;
}要点回顾
- 包装类把基本类型封装成对象,用于集合、泛型等场景
- 六数值类继承
Number,可用统一的xxxValue()转换 valueOf()会复用缓存对象(-128~127),new Integer()不会- 自动装箱/拆箱是语法糖,但有性能和 NPE 风险
- 比较包装类,用
equals()而不是==
记住:基本类型是值,包装类是对象。值比大小用
==,对象比大小用equals()。这是 Java 的基本修养。
