0%

Integer拆装箱问题

Integer 拆装箱与缓存机制:为什么 2 == 2 而 200 != 200?

在 Java 中,Integer 作为 int 的包装类,其拆装箱(Autoboxing/Unboxing)机制和缓存策略常常导致看似矛盾的结果,比如两个值相等的 Integer 对象用 == 比较时,有时为 true,有时为 false。本文将深入解析这一现象背后的原理。

自动拆装箱的基本概念

自动装箱(Autoboxing):将基本数据类型(如 int)自动转换为对应的包装类(如 Integer)。
自动拆箱(Unboxing):将包装类(如 Integer)自动转换为对应的基本数据类型(如 int)。

示例:

1
2
3
4
5
// 自动装箱:int → Integer
Integer a = 2; // 等价于 Integer a = Integer.valueOf(2);

// 自动拆箱:Integer → int
int b = a; // 等价于 int b = a.intValue();

正是自动装箱机制,使得 Integer a = 2 这种语法成立,而其内部依赖 Integer.valueOf(int) 方法实现。

Integer 缓存机制(IntegerCache)

Integer 的缓存机制是导致 2 == 2200 != 200 的核心原因。Java 为了优化性能,对 -128 到 127 之间的整数 进行了缓存,避免频繁创建相同值的 Integer 对象。

1. Integer.valueOf(int) 方法的实现

Integer.valueOf(int) 是自动装箱的核心方法,其源码逻辑如下:

1
2
3
4
5
6
7
public static Integer valueOf(int i) {
// 若 i 在缓存范围内(-128~127),直接返回缓存中的实例
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// 超出范围则创建新的 Integer 实例
return new Integer(i);
}

2. IntegerCache 内部类

IntegerCacheInteger 的静态内部类,负责管理缓存的 Integer 实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// 缓存上限默认为 127,可通过 JVM 参数调整(-XX:AutoBoxCacheMax=xxx)
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 缓存上限不能超过 Integer.MAX_VALUE - (-low)
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 忽略无效参数
}
}
high = h;

// 初始化缓存数组,存储 -128 到 high 的 Integer 实例
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}

private IntegerCache() {}
}

关键特性

  • 缓存范围默认是 -128 ~ 127
  • 缓存上限 high 可通过 JVM 参数 -XX:AutoBoxCacheMax=xxx 调整(如调整为 500),但下限 -128 不可修改;
  • 缓存数组在类加载时初始化,所有在范围内的 Integer 实例被提前创建并存储,后续直接复用。

题目解析:为什么 a == btruec == dfalse

回到开头的题目:

1
2
3
4
5
6
7
Integer a = 2;    // 自动装箱:调用 Integer.valueOf(2)
Integer b = 2; // 自动装箱:调用 Integer.valueOf(2)
Integer c = 200; // 自动装箱:调用 Integer.valueOf(200)
Integer d = 200; // 自动装箱:调用 Integer.valueOf(200)

System.out.println(a == b); // true
System.out.println(c == d); // false

原因分析:

  1. ab 的比较
    2-128 ~ 127 范围内,Integer.valueOf(2) 会返回缓存中的同一个 Integer 实例。因此 ab 指向同一个对象a == btrue
  2. cd 的比较
    200 超出了默认缓存范围(>127),Integer.valueOf(200)创建新的 Integer 实例。因此 cd 指向不同的对象c == dfalse

拓展:其他包装类的缓存机制

Java 中其他基本类型的包装类也有类似的缓存机制,但范围和实现略有不同:

包装类 缓存范围 特点
Byte -128 ~ 127 范围固定,不可修改
Short -128 ~ 127 范围固定,不可修改
Character 0 ~ 127 范围固定,缓存 ASCII 字符
Long -128 ~ 127 范围固定,不可修改
Integer -128 ~ 127(可调整上限) 唯一可通过 JVM 参数修改缓存上限的类
Boolean truefalse 仅缓存两个静态实例

注意事项与最佳实践

  1. ==equals() 的区别

    • == 比较对象引用(地址),仅当两个 Integer 指向同一实例时为 true
    • equals() 比较值,无论是否在缓存范围内,只要值相等就为 true

    示例:

    1
    2
    3
    4
    Integer a = 200;
    Integer b = 200;
    System.out.println(a == b); // false(引用不同)
    System.out.println(a.equals(b)); // true(值相同)
  2. 避免依赖缓存范围
    缓存上限可通过 JVM 参数修改,因此不要在代码中假设 Integer 的缓存范围是固定的 127,比较值时优先使用 equals()

  3. 拆箱后的比较
    Integerint 比较,Integer 会自动拆箱为 int,此时 == 比较的是值:

    1
    2
    3
    Integer a = 200;
    int b = 200;
    System.out.println(a == b); // true(a 自动拆箱为 int,比较值)

欢迎关注我的其它发布渠道