☕ 深入理解 Java Integer 缓存机制:为什么 127 == 127,但 128 != 128?

37 阅读7分钟

#Java #面试题 #Integer缓存 #避坑指南

摘要:

你以为你懂 == 吗?面试官扔出这 7 行代码,90% 的 Java 新手都在第 4 行栽了跟头。本文将带你通过“VIP 休息室理论”,彻底搞懂 Integer 的缓存机制与自动装箱陷阱。

一个看似简单却暗藏玄机的面试题!

今天在复习 Java 包装类的时候,写了 7 个简单的比较判断,结果出乎意料,让我重新审视了自己对 Java 包装类的理解。

先别急着看答案,挡住下面的结果,看看你能做对几道?

🛑 灵魂拷问:这 7 行代码输出什么?

public class WrapperExercise03 {
    public static void main(String[] args) {
        // 示例一:都是 new 出来的
        Integer i1 = new Integer(127);
        Integer i2 = new Integer(127);
        System.out.println(i1 == i2); // Q1: true 还是 false?

        // 示例二:new 128
        Integer i3 = new Integer(128);
        Integer i4 = new Integer(128);
        System.out.println(i3 == i4); // Q2: true 还是 false?

        // 示例三:直接赋值 127 (自动装箱)
        Integer i5 = 127;
        Integer i6 = 127;
        System.out.println(i5 == i6); // Q3: 重点来了!

        // 示例四:直接赋值 128 (自动装箱)
        Integer i7 = 128;
        Integer i8 = 128;
        System.out.println(i7 == i8); // Q4: 最离谱的在这里!

        // 示例五:一个 new 一个直接赋值
        Integer i9 = 127;
        Integer i10 = new Integer(127);
        System.out.println(i9 == i10); // Q5: ?

        // 示例六:Integer 遇到 int (127)
        Integer i11 = 127;
        int i12 = 127;
        System.out.println(i11 == i12); // Q6: ?

        // 示例七:Integer 遇到 int (128)
        Integer i13 = 128;
        int i14 = 128;
        System.out.println(i13 == i14); // Q7: ?
    }
}

🕵️‍♂️ 揭晓答案

运行结果如下:

  1. False

  2. False

  3. True (127 相等)

  4. False (128 竟然不等?)

  5. False

  6. True

  7. True


🔍 深度复盘

是不是有点晕?为什么 127 是 True,到了 128 就变成 False 了?难道 Java 对 128 有偏见?

别急,我们分三层逻辑来扒开它的伪装。


第一层:new 创建对象:堆上的独立实例

看 示例 1、2、5。

当你使用 new Integer() 创建对象时,无论数值是多少,Java 都会在堆内存中创建一个全新的对象:

Integer i1 = new Integer(127);  // 创建一个新对象
Integer i2 = new Integer(127);  // 创建另一个新对象
System.out.println(i1 == i2);   // false:比较的是对象地址

不管里面的值是 127 还是 128,只要你用了 new Integer(),它们就是内存地址完全不同的两个对象。

== 比较的是两个对象在内存中的地址,而不是它们的值。每次 new 都会产生不同的地址,所以结果是 false


第二层:神奇的“VIP 休息室” (Integer Cache)

看 示例 3、4 。

Integer i5 = 127;//自动装箱:实际调用 Integer.valueOf(127)
Integer i6 = 127;//同样调用 Integer.valueOf(127)

这里没有用 new,而是直接赋值:Integer i = 127;。

这行代码背后,Java 编译器偷偷帮我们干了一件事,叫自动装箱 (Auto-boxing)。它实际执行的是:

Integer i = Integer.valueOf(127);

关键就在这个 valueOf() 方法里!我们看看 JDK 的源码逻辑(简化版):

public static Integer valueOf(int i) {
    // IntegerCache.low 默认为 -128
    // IntegerCache.high 默认为127
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)]; // 直接从缓存数组拿!
    
    // 如果超出了这个范围,创建新对象
    return new Integer(i);

这里的关键是 IntegerCache,它是 Integer 类的一个静态内部类。在 JVM 加载 Integer 类时,这个缓存就被初始化了:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
    
    static {
        // 默认上限是 127
        int h = 127;
        // 可以通过 JVM 参数调整上限
        String integerCacheHighPropValue = 
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
            } catch (NumberFormatException nfe) {
                // 忽略格式错误,使用默认值
            }
        }
        high = h;
        
        // 初始化缓存数组
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    }
}

“VIP 休息室理论”(缓存机制原理):

  • Java 官方规定-128127 这 256 个整数是很多程序里最高频使用的数字。

  • VIP 待遇:Java 在启动时,就专门建了一个“VIP 休息室”(缓存数组),把这 256 个对象提前造好了放在里面。

  • 获取对象时:调用 valueOf() 时,如果值在缓存范围内,直接返回缓存中的对象i5i6 拿到的都是同一个对象,地址一样,所以是 True

  • 超出范围时:不是 VIP。Java 只能创建新的 Integer 对象i7i8 是两个刚刚建好的不同房子,地址不同,所以是 False

这就是 Integer.valueOf(127) 返回同一个对象,而 Integer.valueOf(128) 返回不同对象的原因。


第三层:当 int 这种老实人出现

看 示例 6、7。

Integer i11 = 127;
int i12 = 127;
System.out.println(i11 == i12);

Integer (包装类) 和 int (基本类型) 进行比较时,Java 会进行自动拆箱

也就是把 Integer 里的皮剥开,取出里面的数值。这时候,比较的不再是内存地址,而是纯粹的数值大小。所以,只要数值是 128,它就相等,不管你是 VIP 还是普通用户。


💡 实际开发中的注意事项

1. 使用正确的比较方法

// 错误:使用 == 比较包装类对象
Integer a = 200;
Integer b = 200;
if (a == b) {  // false,虽然值相等但对象不同
    // 这里的代码不会执行
}

// 正确:使用 equals() 方法
if (a.equals(b)) {  // true
    // 这里的代码会执行
}

// 或者先拆箱再比较
if (a.intValue() == b.intValue()) {  // true
    // 这里的代码会执行
}

2. 性能考虑

虽然缓存机制节省了内存,但在高频计算中,包装类依然有性能损耗(拆箱/装箱):

// 性能较差:频繁装箱拆箱
Integer sum = 0;
for (int i = 0; i < 1000000; i++) {
    sum += i;  // 每次循环都涉及拆箱和装箱
}

// 性能更好:使用基本类型
int sum = 0;
for (int i = 0; i < 1000000; i++) {
    sum += i;
}

3. 其他包装类的缓存

类似 Integer,其他包装类也有缓存机制,但范围略有不同:

  • 全缓存 (-128 ~ 127)Byte, Short, Long

  • 部分缓存 (0 ~ 127)Character

  • 全缓存 (True/False)Boolean

  • 无缓存Float, Double (因为浮点数是无限的)

// Float 和 Double:没有缓存
Float f1 = 1.0f;
Float f2 = 1.0f;
System.out.println(f1 == f2);  // false

⚙️ 配置缓存范围

在某些特殊的高性能场景下,我们可能需要更大的缓存范围。虽然下限固定为 -128,但我们可以通过 JVM 启动参数 调整 Integer 缓存的上限。

这不是写在 Java 代码里的,而是在运行 Java 程序时传入的参数:

# 将 Integer 缓存上限扩展到 200
java -Djava.lang.Integer.IntegerCache.high=200 MyApp

这样设置后,Integer a = 200; Integer b = 200; 就会变成 true 了。

注意:这个参数只能调整上限,下限固定为 -128。


📝 总结与避坑

  1. 比较对象永远别用 ==:除非你非常清楚自己在比地址,否则比较两个包装类是否相等,一定要用 .equals() 方法

  2. 理解缓存机制:在面试中,这是个必考题。记住 -128 ~ 127 会走缓存,超出范围就是新对象。

  3. 性能优化:在循环或高频调用的代码中,优先使用基本类型int,避免不必要的装箱拆箱操作。

  4. 代码可读性:如果确实需要新对象,明确使用 new Integer()(虽然已过时,但语义清晰);尽量使用 Integer.valueOf() 利用缓存。

理解这些细节不仅能帮助你在面试中表现出色,更能写出更健壮、更高效的 Java 代码。下次遇到类似的包装类比较问题时,你会清楚知道背后发生了什么。