Java学习篇之---小整数缓存机制
这是一个有意思的话题。如果你运行如下代码:
Integer a = 1000, b = 1000;
System.out.println(a == b);//1
Integer c = 100, d = 100;
System.out.println(c == d);//2
你将会得到:
false
true
正如我们所知,如果两个引用指向同一个对象,按照“==”来判断两者是相等的。如果两个引用指向两个不同的对象,那么按照"=="来判断两者是不等的,即使两者有相同的引用内容。
因此最后一条语句也应该是false。
这就是有趣之处了。如果你去看Integer.java类,你会发现有一个内部私有类IntegerCache.java,它缓存了从-128~127之间的所有整数对象。
所以事情就是,所有的小整数都被在内部进行缓存。例如当我们声明如下:
Integer c = 100;
它实际上内部进行的操作是这样的:
Integer i = Integer.valueOf(100);
如果我们去看valueOf()方法,我们可以看到:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
如果值在-128~127之间,那么它就会从高速缓存中返回实例。
所以:
Integer c = 100, d = 100;
指向了同一个对象。
这就是为什么当我们写如下代码后我们获得true的原因:
System.out.println(c == d);
现在你可能会问,为什么这里需要缓存?
合理的解释是这样的:在此范围内的小整数比大整数使用率要高,所以为了减少潜在的内存占用,使用相同的底层对象是有价值的。
然而,通过反射API你会误用此功能。
运行起来,享受以下代码吧:
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Class cache = Integer.class.getDeclaredClasses()[0]; //1
Field myCache = cache.getDeclaredField("cache"); //2
myCache.setAccessible(true);//3
Integer[] newCache = (Integer[]) myCache.get(cache); //4
newCache[132] = newCache[133]; //5
int a = 2;
int b = a + a;
System.out.printf("%d + %d = %d", a, a, b); //
}
运行结果:
对以上代码的解释:
正常情况下newCache[132]中存放的是4,newCache[133]中存放的是5,现在用newCache[133]中的值替换newCache[132]中的值。这样的话计算完毕之后发现结果是4,于是系统就会去默认应该缓存4的地址上取值,但是此时由于已经将原本是值4地址上的值换成了5,所以结果是5。
另外需要注意的是:
System.out.printf("%d + %d = %d", a, a, b); //
和
System.out.println(a+" + "+a+" = "+b ); //
结果是不一样的。参考println的方法声明,作者对于不同的基本类型都提供了overwrite,而printf并没有为基本类型提供方法。
问题的本质在于:println(2+2)的结果仍是基本类型,并没有进行装箱,结果为4;System.out.println((Integer)2+2);的结果为5。
以上内容大部分翻译自:https://dzone.com/articles/why-1000-1000-returns-false-but-100-100-returns-tr