Java基础——包装类缓存
在开始之前,我们先来看一下Java语言规范(JLS7-5.1.7)中的一小段内容:
If the value p being boxed is true, false, a byte, or a char in the range \u0000 to \u007f, or an int or short number between -128 and 127 (inclusive), then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.
大致意思:
对boolean类型的值、byte类型的值、\u0000到\u007f(含)范围内的值、以及-128到127(含)范围内的int或者short类型的值进行自动装箱操作时,需要保证对字面量p进行任意两次装箱操作得到的对象r1和2都满足r1==r2。
结合上边的Java语言规范,我们来猜测一下下面代码的执行结果
Integer num = 127;
Integer num2 = 127;
System.out.println(num == num2);
System.out.println(num.equals(num2));
Integer num5 = new Integer(127);
System.out.println(num == num5);
System.out.println(num.equals(num5));
Integer num3 = 128;
Integer num4 = 128;
System.out.println(num3 == num4);
System.out.println(num3.equals(num4));
这段代码的执行结果如下:
true
true
false
true
false
true
问题来了,为什么表示同样数字的Integer对象,在使用“==”和equals进行判断时,得出的结果确实如此的多变?其实这主要是因为Java的自动装箱和包装类缓存的共同作用引起的。自动装箱在的文章已经做了说明这里不再赘述,不清楚的朋友可以移步(这里)。
什么是包装类缓存呢 🤔?
还记得文章开头引用的一部分Java语言规范吗,包装类缓存就是为了实现这部分的语言规范而设计的。除了Float和Double外,其他的包装类内部都实现了针对其表示的基本类型的缓存机制,我们这里以Integer包装类的缓存为例来进行分析。下面我来一起看下自动装箱时调用的Integer.valueOf(int) 方法:
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
从上面的源码中我们可以看出,当定义的整数在一定范围内时,不论在什么地方定义,实际上返回的都是IntegerCache中初始化好的同一个对象。只有在整数值超出这个数组范围或者直接调用构造函数时,才会生成新的Integer对象。那么这个缓存的数值范围到底有多大呢,我一起来看一下。
首先我们来看一下IntegerCache的源代码:
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
int h = 127;
String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
// 数组的大小上限为Integer.MAX_VALUE,缓存数值的最大值为Integer.MAX_VALUE
h = Math.min(i, 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
VM.initializeFromArchive(IntegerCache.class);
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;
for (int k = 0; k < c.length; k++)
c[k] = new Integer(j++);
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
通过代码中for循环的部分可以看出,IntegerCache的缓存范围时通过参数low和high来控制的。而参数low定义为静态常量且值为-128,也就是说缓存的下限是无法修改;参数high则取127和java.lang.Integer.IntegerCache.high两者中的较大值(最大不能超过2147483518),想要自定义java.lang.Integer.IntegerCache.high的话,可以通过JVM启动参数-XX:AutoBoxCacheMax=来设置。
总结
- Java的包装类默认缓存了true、false、-128~127、
- 包装类对象之间进行相等判断时,要使用equals方法,决不能使用“==”
- 可以通过JVM启动参数-XX:AutoBoxCacheMax=<size>来设置整数的缓存上限