Integer

Integer 源码分析

int 的包装类,让基本类型能进集合、泛型和一切只认对象的场合。核心设计只有两件事:不可变(value 是 final),以及自动装箱走 valueOf 时的 [-128, 127] 缓存池,所有 == 陷阱都从这个池子来。

代码块JAVA · 101 行收起展开
// 基于 JDK 25 (本地 JAVA_Source 仓), java.lang.Integer
@jdk.internal.ValueBased              // 值类型候选:别拿 Integer 当锁,缓存对象是全局共享的
public final class Integer extends Number
        implements Comparable<Integer>, Constable, ConstantDesc {

    @Native public static final int   MIN_VALUE = 0x80000000;   // @Native: 该常量会生成进 JNI 头文件供 C 代码使用

    @Native public static final int   MAX_VALUE = 0x7fffffff;

    // ...

    private static final class IntegerCache {
        static final int low = -128;   // 下界写死:JLS 5.1.7 只强制 [-128, 127],即 byte 的全域
        static final int high;         // 上界只能调大,不能调小

        @Stable                        // 提示 JIT:数组元素一旦非 null 就不再变,读取可被常量折叠
        static final Integer[] cache;
        static Integer[] archivedCache;    // CDS 共享归档里的缓存,多个 JVM 进程共用同一份只读内存

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");  // -XX:AutoBoxCacheMax 最终也落到这个属性
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);  // 想调小?127 兜底,调不动
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);   // 归档命中时 256 个对象一个都不用 new
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                // If archive has Integer cache, we must use all instances from it.
                // Otherwise, the identity checks between archived Integers and
                // runtime-cached Integers would fail.
                int archivedSize = (archivedCache == null) ? 0 : archivedCache.length;
                for (int i = 0; i < archivedSize; i++) {
                    c[i] = archivedCache[i];   // 归档实例必须原样复用,否则 == 语义会因 CDS 开关而变
                    assert j == archivedCache[i];
                    j++;
                }
                // Fill the rest of the cache.
                for (int i = archivedSize; i < size; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

    @IntrinsicCandidate                // JIT 可用内建实现替换;配合逃逸分析,装箱可能被整个消除
    public static Integer valueOf(int i) {     // 自动装箱 Integer a = 100 编译后调的就是它
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];  // 命中池子:永远同一个对象
        return new Integer(i);                                   // 出了池子:每次新对象,== 必然 false
    }

    private final int value;           // final 是 Integer 不可变的全部来源:可安全做 HashMap key、跨线程共享

    @Deprecated(since="9")
    public Integer(int value) {        // 直接 new 绕过缓存池,官方已废弃,永远用 valueOf
        this.value = value;
    }

    @IntrinsicCandidate
    public int intValue() {            // 自动拆箱调的就是它,null 拆箱的 NPE 就抛在对它的调用上
        return value;
    }

    public boolean equals(Object obj) {
        if (obj instanceof Integer i) {    // 严格判类型:传 Long/null 直接 false,不做跨类型比较
            return value == i.intValue();
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    public static int hashCode(int value) {
        return value;                  // hash 就是值本身:完美散列,零冲突
    }
}

parseInt 是字符串进 int 的唯一入口,最反直觉的一点是全程在负数域累加:

代码块JAVA · 55 行收起展开
// 基于 JDK 25 (本地 JAVA_Source 仓), java.lang.Integer
public final class Integer extends Number
        implements Comparable<Integer>, Constable, ConstantDesc {
    // ...

    public static int parseInt(String s, int radix)
                throws NumberFormatException {
        /*
         * WARNING: This method may be invoked early during VM initialization
         * before IntegerCache is initialized. Care must be taken to not use
         * the valueOf method.
         */

        if (s == null) {
            throw new NumberFormatException("Cannot parse null string");
        }

        // ... radix 越界检查省略:不在 [Character.MIN_RADIX, MAX_RADIX] 即 [2, 36] 内直接抛 NumberFormatException

        int len = s.length();
        if (len == 0) {
            throw NumberFormatException.forInputString("", radix);
        }
        int digit = ~0xFF;             // 哨兵值:区分"首字符是符号"和"首字符是数字"两条路
        int i = 0;
        char firstChar = s.charAt(i++);
        if (firstChar != '-' && firstChar != '+') {
            digit = digit(firstChar, radix);   // 静态导入的 Character.digit,非法字符返回 -1
        }
        if (digit >= 0 || digit == ~0xFF && len > 1) {   // 只有符号没有数字位("-"/"+")不放行
            int limit = firstChar != '-' ? MIN_VALUE + 1 : MIN_VALUE;  // 负域比正域宽 1,正数的界是 -(MAX_VALUE)
            int multmin = limit / radix;
            int result = -(digit & 0xFF);      // 符号开头: ~0xFF & 0xFF == 0;数字开头: 就是首位数字取负
            boolean inRange = true;
            /* Accumulating negatively avoids surprises near MAX_VALUE */
            while (i < len && (digit = digit(s.charAt(i++), radix)) >= 0
                    && (inRange = result > multmin
                        || result == multmin && digit <= radix * multmin - limit)) {  // 乘之前先判溢出
                result = radix * result - digit;   // 负数域累加,MIN_VALUE 也装得下
            }
            if (inRange && i == len && digit >= 0) {
                return firstChar != '-' ? -result : result;   // 正数最后才翻转符号
            }
        }
        throw NumberFormatException.forInputString(s, radix);
    }

    public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s, 10);
    }

    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));   // 先解析成 int 再装箱,照样享受缓存池
    }
}

装箱缓存导致的经典陷阱:

代码块JAVA · 7 行收起展开
Integer a = 127, b = 127;
System.out.println(a == b);      // true:都命中缓存池,同一个对象
Integer c = 128, d = 128;
System.out.println(c == d);      // false:各 new 一个对象
System.out.println(c.equals(d)); // true:equals 比的是值
int e = 128;
System.out.println(c == e);      // true:一边是 int 时 c 先拆箱,变成纯值比较

原理串讲

代码块JAVA · 3 行收起展开
一次 `Integer a = 127; Integer b = 127; a == b` 的完整链路:javac 把 `Integer a = 127` 编译成 `Integer.valueOf(127)`(字节码里是一条 invokestatic),首次调用触发 IntegerCache 类加载。
static 块先读 `java.lang.Integer.IntegerCache.high` 决定上界(`-XX:AutoBoxCacheMax=1000` 会把它抬到 1000,下界永远 -128),再通过 `CDS.initializeFromArchive` 尝试从共享归档直接映射整个 cache 数组,命中时连这 256 个对象都不用 new,且必须原样复用归档实例,否则同一个 127 在开关 CDS 的两次运行里 `==` 结果会不一样。
之后 `valueOf(127)` 落在 `[low, high]` 内,返回 `cache[127 + 128]`,a 和 b 拿到同一个引用,`==` 为 true;换成 128 则两次 `new Integer(128)`,引用必不同。

反方向 int x = a 编译成 a.intValue(),a 为 null 时 NPE 就抛在这里,三目运算符里混用 int 和 Integer 触发自动拆箱是这个 NPE 的高发现场。

为什么缓存只强制 [-128, 127]?JLS 5.1.7 的承诺是”这个范围内装箱结果必须可复用”,因为 byte 全域小到可以无条件预热,而真实程序里的小整数(循环变量、状态码、下标)又占绝对多数,收益最大。
上界可调、下界不可调也是同一逻辑:扩大池子不破坏规范承诺,缩小就破坏了,所以源码里 Math.max(parseInt(...), 127) 直接把调小的企图顶回去。

为什么 parseInt 在负数域累加?int 的负域比正域多一个数:MIN_VALUE 是 -2^31 而 MAX_VALUE 是 2^31-1。
若按正数累加,解析 “-2147483648” 时中间值 2147483648 已经溢出;在负数域累加则正负输入都装得下,最后按 firstChar 决定翻不翻转符号。
溢出检查也顺势前置:每轮乘 radix 之前先和 multmin 比较,把”乘完才发现溢出”变成”乘之前就能判定”,循环体内不需要任何 long 或额外分支。

为什么 valueOf(String) 不自己 new?它拆成 parseInt + valueOf(int) 两步,字符串解析和对象获取正交,解析出来的小整数照样走缓存。
所有通往 Integer 对象的路最终都汇到 valueOf(int) 这一个口子,这是缓存语义能守住的前提,也是构造器被 @Deprecated 的根本原因:new 出来的对象永远不进池,等于单方面撕毁 == 的可复用承诺。

设计取舍

  • 值比较永远用 equals 或先拆成 int。== 只在两边都命中缓存池时碰巧为 true,128 就翻车。
  • equals 严格判类型:Integer.valueOf(1).equals(1L) 是 false。跨类型比较前先统一成基本类型。
  • 不可变意味着 i++ 实为拆箱、加一、再装箱出新对象。热路径累加用 int,别用 Integer。
  • 别对 Integer 加 synchronized:缓存池对象全局共享,两段无关代码锁同一个 Integer 42 会互相阻塞,@ValueBased 类未来会直接禁止当锁。
  • new Integer(...) 自 JDK 9 起废弃,它绕过缓存池且骗过一切依赖对象复用的假设。

延伸阅读