128陷阱深度详解——Integer自动拆箱的艺术
1.概述
127陷阱指 Integer类封装的数字在[-128,127]范围内比较可以相等,超过此范围不能相等的现象。示例代码如下
Integer a= 128;
Integer b= 128;
System.out.println(a==b);
Integer e= 100;
Integer f= 100;
System.out.println(e==f);
Integer c= -129;
Integer d= -129;
System.out.println(c==d);
打印效果
false
true
false
2.原理
要了解其中原理,首先理解几个概念。
2.1 对象值相等,对象地址相等
对于两个一般的引用对象,例如
对象A :Student{age=15,name="小明"}
对象B :Student{age=15,name="小明"}
地址相等意味着两个引用对象指向堆中同一块地址,修改A的属性,B也会相应变化。
值相等意味着两个对象内容是一致的。A的各个属性都与B的属性相等。示例中A和B的age都是15,name都是小明,无论AB是否地址相等,我们可以直观看到AB的各个属性一致。。
此外,从程序运行角度来看,如果打印A==B结果为true,则认为两个对象的地址是相等的。如果打印A.equals(B)结果为true,则认为两个对象的值是相等的。
2.2 自动拆箱
和上例中的Student类不同,Integer类包含一个静态方法valueOf(int i),当两个Integer类的实例
Integer a= 128;
Integer b= 128;
用两个等号==相比较时,比较的是valueOf的返回值,这个过程称为自动拆箱。 进入Integer类源码,找到valueOf()方法如下:
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是“128陷阱”的关键。当int i超过某个范围时,返回一个新的对象,即
return new Integer(i);
否则,如果i在约束的范围内(范围很可能时我们上面提到的[-128,127]),返回某个固定地址的值。即
return IntegerCache.cache[i + (-IntegerCache.low)];
3."128陷阱"实现机制
由此,我们可以推出,两个超过[-128,127]范围的Integer类对象比较时,比较了两个new Integer(i)的地址,他们是不可能相等的。如果不超过范围[-128, 127],则是比较两个“常量”对象的地址,即IntegerCache.cache[n],如果值相等,地址必然相等,A==B的返回值也就一定是true了。
下面我们进入Integer源码,观察IntegerCache.low 和IntegerCache.high是如何工作的。
4.IntegerCache工作机制
4.1 源码概述
IntegerCache类的完整源码如下,之后逐一分析:
这段源码表示,IntegerCache是Integer的静态内部类。该类加载时,需要依次执行“static”修饰的语句或块,称为静态初始化语句、静态初始化块。
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() {}
}
4.2 定义下界-128
容易发现,IntegerCache.low是-128,即128陷阱[-128,127]的下界。
static final int low = -128;
这行语句由static修饰,叫做静态初始化语句。final修饰表示low这个变量赋值为-128后,不能再被其他语句改变了。
4.3 定义上界127
static final int high;
static final Integer cache[];
之后两行语句意味着high和cache被声明,并且之后仅可以赋值一次。
执行完毕三行初始化语句,开始执行初始化块,如下。
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
读取某个配置文件中的内容。
如果读取到内容,则把该值和127比较取较大值,作为“128陷阱”的上界,即上文提到的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;
4.4 定义缓存
定义好上界(根据配置文件,可能大于127)和下界(指定为-128)后,新建缓存区,即cache。缓冲区的大小由上界IntegerCache.high、下届IntegerCache.low共同决定。
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
至此,IntegerCache的成员变量全部定义完毕。由于不存在任何非静态变量,不用创建IntegerCache类的对象即可引用到所需内容(上界、下界、缓存区)。
5.总结
综上,我们可以看到,128陷阱的实现主要利用自动拆箱。Integer的自动拆箱,使用了上文2.2提到的valueOf方法,Integer类的实例A、实例B使用双等号比较,比较的实际上是经过Integer类处理过的地址,而这个处理过程主要依托于IntegerCache类。IntegerCache类初始化128陷阱的上界、下界和缓存,最后才能达到“128陷阱”的效果。
建议读者翻到博客开头,通过第一节示例代码思考、回忆Integer自动拆箱流程。