现象

我们先运行一段代码,代码片段如下:

Integer a = 1000, b = 1000;
System.out.println(a == b);//1
Integer c = 100, d = 100;
System.out.println(c == d);//2

如果基础比较好的同学,一眼就能看出来,这段代码结果为falsetrue,即两个值为1000的Integer不想等,但是两个值为100的Integer却是想等的。

原作者这里分享了一段基础常识:

如果两个引用指向同一个对象,那么==判断是为true,如果指向不同对象,那么==判断就是false,即使它们的内容是完全一样的。

但是,如果按照这个基础常识来判断的话,上面那段代码中c==d也应该是false才对,说明JDK源代码肯定在这里动了一些手脚,我们接着往下看!

原因

如果你看过Integer.java的源码就会知道,它里面有一个内部私有类IntegerCache.java,它缓存了所有[-128,127]之间的Integer对象:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        int i = parseInt(integerCacheHighPropValue);
        // 最大值不允许小于127
        i = Math.max(i, 127);
        // 最大值不允许超过 Integer.MAX_VALUE
        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++){
            cache[k] = new Integer(j++);
        }

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

而我们平常申明一个Integer变量的代码:Integer i = 100,它背后的代码实际上是Integer i = Integer.valueOf(100)。因为Java有自动装箱处理这个语法糖,所以我们简单申明即可。而Integer.valueOf()源码如下,它优先从IntegerCache中获取,获取不到的情况下才会new一个Integer。所以,只要是cache中的Integer对象比较,==的结果都会是true:

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

为什么

现在可能你会想问,为什么需要cache?因为,根据统计在这个范围内的小整数要比这个范围外的大整数使用频率高很多。所以,cache很有必要,它能显著减少内存开销。

有趣的代码

接下来,原作者还给了一段很有意思的代码,源码如下:

public static void main(String[] args) throws Exception {
    Class cache = Integer.class.getDeclaredClasses()[0]; //1
    Field myCache = cache.getDeclaredField("cache"); //2
    myCache.setAccessible(true);//3
    // 通过反射获取到Integer中cache了的对象集合
    Integer[] newCache = (Integer[]) myCache.get(cache); //4
    // 因为默认从-128缓存到127,所以下标为133对应的就是5,132对应的是4
    newCache[132] = newCache[133]; //5
    int a = 2;
    int b = a + a;
    System.out.printf("%d + %d = %d", a, a, b); 
}

代码解读:b的结果本来应该是4,这个很容易验证。最后执行System.out.println(b);即可。但是System.out.printf("%d + %d = %d", a, a, b); 的结果却是2 + 2 = 5,这是因为前一个println方法可以接受int类型参数,意味着它不用自动装箱。而后一个printf方法接受的参数是Object类型,意味着int类型变量需要自动装箱为Integer,而本来的4即newCache[132]已经被反射的代码改成newCache[133]即5,所以我们看到的是2 + 2 = 5。

魔法

最后,笔者再来加点小魔法,让两个值为1000的Integer变量==为true,甚至只要你愿意,让两个任意值的Integer变量都可以==为true。如何控制呢?非常简单,我们看一下IntegerCache.java这个类的注释就明白了:

* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
* The cache is initialized on first usage.  The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the sun.misc.VM class.

通过这段注释,我们只需要配置JVM参数:-XX:AutoBoxCacheMax=1000,就能让两个值为1000的Integer对象==为true了,一个非常简单但是非常神奇的JVM参数,你get到了嘛?

发表评论

电子邮件地址不会被公开。 必填项已用*标注