包装类与缓存池

面试官问:"Integer 的缓存池范围是多少?"

候选人小郑答:"-128 到 127。"

面试官追问:"为什么是这个范围?"

小郑说:"JVM 规范的默认配置。"

面试官追问:"能用参数调整吗?"

小郑说:"可以..."

面试官又问:"那 String 的常量池和 Integer 的缓存池有什么区别?"

小郑答不上来。

【面试官心理】 这道题考查的是候选人对 Java 内存管理和自动装箱机制的理解。能说出缓存池范围可配置、以及与 String 常量池区别的候选人,说明对 JVM 有深入了解。

一、Integer 缓存池 🔴

1.1 valueOf 源码

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

// IntegerCache 静态内部类
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // 默认 high = 127
        // 可以通过 -XX:AutoBoxCacheMax=<size> 配置
        int h = 127;
        String integerCacheHighPropValue =
            VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        // ... 处理配置
        high = h;
        cache = new Integer[(high - low) + 1];
        for (int i = 0; i < cache.length; i++)
            cache[i] = new Integer(i + low);
    }
}

1.2 缓存范围

// 默认:-128 ~ 127
Integer a = 100;
Integer b = 100;
a == b; // true!来自缓存池

Integer c = 128;
Integer d = 128;
c == d; // false!超出缓存范围,创建了新对象

1.3 缓存池是可配置的

// 启动参数:-XX:AutoBoxCacheMax=256
// 缓存范围变为:-128 ~ 256

// 注意:这个参数只影响上界,下界永远是 -128

二、所有包装类的缓存池 🔴

包装类缓存范围说明
Byte-128 ~ 127固定,只有 256 个值
Short-128 ~ 127默认,不可配置
Integer-128 ~ 127默认,可通过参数配置
Long-128 ~ 127默认,不可配置
Character0 ~ 1270 ~ '\u007F',不可配置
Booleantrue, false只有两个值
Float/Double无缓存每次创建新对象
// Byte
Byte b1 = 100;
Byte b2 = 100;
b1 == b2; // true

// Character
Character c1 = 65; // 'A'
Character c2 = 65;
c1 == c2; // true

// Double(无缓存)
Double d1 = 1.0;
Double d2 = 1.0;
d1 == d2; // false!没有缓存,每次创建新对象

三、自动装箱与缓存 🟡

3.1 自动装箱的陷阱

// 自动装箱 = valueOf
Integer a = 127; // Integer.valueOf(127)
Integer b = 127;
a == b; // true

Integer c = 128; // Integer.valueOf(128)
Integer d = 128;
c == d; // false

3.2 包装类的 equals vs ==

// ✅ 正确:使用 equals 比较值
Integer a = 128;
Integer b = 128;
a.equals(b); // true

// ❌ 错误:使用 == 比较值
a == b; // false

// ⚠️ 危险:混合比较
int x = 128;
Integer y = 128;
x == y; // true!自动拆箱,int 和 Integer 比较
// x == y.intValue()

3.3 数组不是缓存的

// 注意:Integer[] 数组不会自动缓存
Integer[] arr1 = {127};
Integer[] arr2 = {127};
arr1[0] == arr2[0]; // false!数组元素是对象,不是缓存

四、String 常量池 vs 包装类缓存池 🟡

维度String 常量池Integer 缓存池
存储位置堆(方法区中)堆(作为 Integer 数组)
创建方式字面量、intern()自动装箱(-128~127)
大小取决于运行时常量默认 256 个
可配置不可配置上界可配置
作用节省字符串内存节省自动装箱开销
// String 常量池
String s1 = "hello"; // 在常量池
String s2 = "hello"; // 复用常量池中的对象
s1 == s2; // true

// Integer 缓存池
Integer i1 = 127; // 从缓存池取
Integer i2 = 127; // 复用缓存池中的对象
i1 == i2; // true

// 两者的共同点:都是通过 == 比较时返回 true
// 不同点:String 可以用 intern() 动态加入常量池

五、生产避坑 🟡

5.1 switch 的自动装箱

// switch 支持自动装箱(JDK 7+)
Integer num = 2;
switch (num) {
    case 1:
        System.out.println("one");
        break;
    case 2:
        System.out.println("two"); // 输出
        break;
}

// 但超出缓存范围时要小心:
Integer num2 = 128; // 不在缓存范围
// switch 可能出问题(JDK 7 之前 switch 只支持 byte、short、char、int)

5.2 HashMap 的 key 问题

// 用 Integer 作为 HashMap 的 key
Map<Integer, String> map = new HashMap<>();
map.put(127, "hello");
map.put(128, "world");

// ✅ get 操作正常
map.get(127); // "hello"
map.get(128); // "world"

// ⚠️ 警告:
// 127 在缓存范围,可能和其他代码中的 127 是同一对象(== 可能生效)
// 128 不在缓存范围,== 不会生效

六、追问升级

面试官:"为什么包装类要设计缓存池?"

// 原因:
// 1. 减少内存分配:自动装箱频繁时,避免创建大量相同值的对象
// 2. 提高性能:缓存命中时不需要 new Integer()
// 3. 历史原因:Java 1.5 引入自动装箱时,保持了 Integer.valueOf() 的缓存行为

// 注意:Byte、Short、Long 的缓存范围都固定为 -128~127
// 只有 Integer 的上界可以通过参数调整

【面试官心理】 能说出"减少内存分配"和"提高性能"两个原因的候选人,说明理解了缓存池的设计意图。这是 P6 的标准要求。