要理解什么是“128陷阱”,首先来看一段代码:
public static void main(String... strings) {
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
}
从代码上看,两个if判断的执行逻辑相同,结果理应相同,而如果你有一定的Java基础,不难得出两个判等操作的结果应该都是false。因为Integer是一个包装类,而“==”进行判断非基础类型变量大小的逻辑是比较其地址是否相同。但是实际运行结果呢?
造成这种“矛盾”的原因,就是所谓的“128陷阱”。
要了解“128陷阱”,首先我们要知道什么是“基本数据类型”及其对应的“包装类”。
我们知道,Java语言中,有8种基本数据类型,但是Java作为一门“面向对象”的语言,使用不属于“对象”的数据类型有时会带来很多麻烦。比如,当我们使用集合类时,我们必须在集合中放入Object类型的元素,也就是我们不能直接把int、double等类型的元素放到集合里。这时候,它们的包装类就产生了。基本数据类型和其对应的包装类型相互转换的过程,就是所谓的“装箱”“拆箱”操作。
基本数据类型 | 对应的包装类型 |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
boolean | java.lang.Boolean |
char | java.lang.Character |
为了减轻开发人员的压力,Java为我们提供了自动拆装箱的功能,例如如下代码的写法是不会报错的。
Integer i =10; //装箱
int b= i; //拆箱
而就是这种自动拆装箱的功能,为我们带来了“128陷阱”。在Java 5中,Integer和int对象通过使用相同的对象引用实现了缓存和重用,从而达到节省内存、提高性能的目的。
我们来看下面这段代码,运行结果已经注释在上面了。
int f = 128;
int f1 = 128;
int g = 127;
Integer g0 = 127;
Integer g1 = 127;
Integer h = 128;
Integer h1 = 128;
System.out.println(f == f1); //t
System.out.println(g == g1); //t
System.out.println(g0 == g1); //t
System.out.println(h == h1); //f
System.out.println(h.equals(h1)); //t
大多数情况下,使用“==”进行比较时,如果比较的是基本数据类型,那么会直接比较值是否相同,即第8行代码;如果比较的是引用类型,那么则会比较地址是否相同,如第10行代码。因此对引用类型的比较我们通常会使用equals()来进行,即第12行代码。这些都没有问题。但是可以看到第11行代码同样是引用类型之间的比较,但是却得到了true的结果。这就是128陷阱。
事实上,在valueOf()方法中,当整数值在-128~127之间时,数值都存储在缓存中,当需要自动装箱时,如果数字在该范围内,就会直接使用缓存中的对象,而不会再重新创建对象,因此相同的值的数据会有同样的地址。在Java 6中,我们可以使用java.lang.Integer.IntegerCache.high来根据实际情况设值该范围的最大值。
以上就是从原理层面对“128陷阱”产生的原因进行的解释,接下来我们从底层源码上看。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到,当数值介于IntegerCache.low和IntegerCache.high之间时,就会直接从IntegerCache.cache中获取对象,否则才会创建一个新对象。
而在IntegerCache中,定义了low的值等于-128,而最大值默认为127,开发人员可以通过AutoBoxCacheMax自行修改。 缓存通过for循环实现,存储在一个整数数组中。这个缓存会在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");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is 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;
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对象。事实上这种机制存在于所有整数类型的类中,其中Byte
、Short、
Long的
固定范围均为-128 =到127且不可更改。