问题

两个 Integer 对象比较时,为什么 100 等于 100 而 1000 不等于 1000?

答案

核心结论

这是因为 Integer 缓存池机制

  • Java 会缓存 -128 到 127 之间的 Integer 对象
  • 在这个范围内,Integer.valueOf() 返回的是同一个对象
  • 超出范围则每次创建新对象

因此:

  • Integer a = 100; Integer b = 100;a == btrue(同一对象)
  • Integer c = 1000; Integer d = 1000;c == dfalse(不同对象)

代码验证

public class IntegerCacheDemo {
    public static void main(String[] args) {
        // 范围内:使用缓存对象
        Integer a = 100;
        Integer b = 100;
        System.out.println("a == b: " + (a == b));           // true
        System.out.println("a.equals(b): " + a.equals(b));   // true

        // 范围外:创建新对象
        Integer c = 1000;
        Integer d = 1000;
        System.out.println("c == d: " + (c == d));           // false
        System.out.println("c.equals(d): " + c.equals(d));   // true

        // 边界测试
        Integer e1 = -128;
        Integer e2 = -128;
        System.out.println("e1 == e2: " + (e1 == e2));       // true

        Integer f1 = 127;
        Integer f2 = 127;
        System.out.println("f1 == f2: " + (f1 == f2));       // true

        Integer g1 = 128;
        Integer g2 = 128;
        System.out.println("g1 == g2: " + (g1 == g2));       // false

        // 使用 new 关键字:总是创建新对象
        Integer h1 = new Integer(100);
        Integer h2 = new Integer(100);
        System.out.println("h1 == h2: " + (h1 == h2));       // false
        System.out.println("h1.equals(h2): " + h1.equals(h2)); // true
    }
}

原理分析

1. 自动装箱机制

// 自动装箱(编译器自动转换)
Integer a = 100;
// 等价于
Integer a = Integer.valueOf(100);

2. 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);
}

3. IntegerCache 内部类

private static class IntegerCache {
    static final int low = -128;
    static final int high;  // 默认 127,可通过 JVM 参数调整
    static final Integer cache[];

    static {
        // 可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size> 调整上限
        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);  // 最小为 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++);

        // 断言确保缓存正确
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

为什么是 -128 到 127?

  1. 常用范围:这个范围覆盖了大部分日常使用的整数
  2. 性能优化:避免频繁创建小整数对象,节省内存
  3. 历史原因:byte 类型的范围就是 -128 到 127

其他包装类的缓存机制

包装类 缓存范围 是否可调整
Integer -128 ~ 127 ✅ 可通过 JVM 参数调整上限
Long -128 ~ 127 ❌ 固定
Short -128 ~ 127 ❌ 固定
Byte -128 ~ 127 ❌ 固定(全部缓存)
Character 0 ~ 127 ❌ 固定
Boolean true, false ❌ 固定(只有两个对象)
Float ❌ 无缓存 -
Double ❌ 无缓存 -

代码验证其他包装类

public class WrapperCacheDemo {
    public static void main(String[] args) {
        // Long 缓存
        Long l1 = 100L;
        Long l2 = 100L;
        System.out.println("Long 100: " + (l1 == l2));  // true

        Long l3 = 1000L;
        Long l4 = 1000L;
        System.out.println("Long 1000: " + (l3 == l4)); // false

        // Character 缓存
        Character c1 = 'A';  // ASCII 65
        Character c2 = 'A';
        System.out.println("Char A: " + (c1 == c2));    // true

        Character c3 = '中';  // 超出 127
        Character c4 = '中';
        System.out.println("Char 中: " + (c3 == c4));   // false

        // Boolean 缓存(只有两个对象)
        Boolean b1 = true;
        Boolean b2 = true;
        System.out.println("Boolean true: " + (b1 == b2)); // true

        // Double 无缓存
        Double d1 = 100.0;
        Double d2 = 100.0;
        System.out.println("Double 100.0: " + (d1 == d2)); // false
    }
}

调整 Integer 缓存上限

可以通过 JVM 参数调整缓存上限(但不能调整下限):

# 将缓存上限调整为 1000
java -XX:AutoBoxCacheMax=1000 IntegerCacheDemo
public class CustomCacheDemo {
    public static void main(String[] args) {
        // 如果设置了 -XX:AutoBoxCacheMax=1000
        Integer a = 1000;
        Integer b = 1000;
        System.out.println("1000 == 1000: " + (a == b));  // 可能为 true
    }
}

常见陷阱与最佳实践

陷阱 1:混淆 == 和 equals

Integer a = 100;
Integer b = 100;
Integer c = 1000;
Integer d = 1000;

// ❌ 错误:使用 == 比较对象
System.out.println(a == b);  // true(碰巧相等)
System.out.println(c == d);  // false(不相等)

// ✅ 正确:使用 equals 比较值
System.out.println(a.equals(b));  // true
System.out.println(c.equals(d));  // true

陷阱 2:自动装箱与手动创建

Integer a = 100;              // 自动装箱,使用缓存
Integer b = Integer.valueOf(100);  // 使用缓存
Integer c = new Integer(100); // 手动创建,不使用缓存

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

陷阱 3:空指针异常

Integer a = null;
int b = a;  // NullPointerException(自动拆箱)

// 安全做法
Integer a = null;
int b = (a != null) ? a : 0;

最佳实践

public class BestPractice {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 1000;

        // ✅ 推荐:使用 equals 比较包装类
        if (a.equals(b)) {
            System.out.println("相等");
        }

        // ✅ 推荐:使用 Objects.equals 避免空指针
        Integer c = null;
        if (Objects.equals(a, c)) {
            System.out.println("相等");
        }

        // ✅ 推荐:基本类型比较用 ==
        int x = 100;
        int y = 100;
        if (x == y) {
            System.out.println("相等");
        }

        // ❌ 不推荐:使用 new Integer()(已过时)
        Integer d = new Integer(100);  // 编译警告

        // ✅ 推荐:使用 Integer.valueOf()
        Integer e = Integer.valueOf(100);
    }
}

面试要点总结

  1. 缓存范围
    • Integer 缓存 -128 到 127
    • 可通过 -XX:AutoBoxCacheMax 调整上限
  2. 原理
    • 自动装箱调用 Integer.valueOf()
    • 范围内返回缓存对象,范围外创建新对象
  3. 比较规则
    • == 比较对象引用(地址)
    • equals() 比较对象值
    • 包装类必须用 equals 比较
  4. 其他包装类
    • Long、Short、Byte、Character 也有缓存
    • Float、Double 无缓存
  5. 最佳实践
    • 包装类比较用 equals()Objects.equals()
    • 避免使用 new Integer()(已过时)
    • 注意自动拆箱的空指针风险
// 记忆口诀
// -128 到 127,Integer 有缓存
// 范围内相等,范围外不等
// 包装类比较,equals 最安全

这道题考察的是对 Java 自动装箱、缓存池机制和对象比较的深入理解,是面试中的经典题目。