Java 设计当初就提供了 8 种 基本数据类型及对应的 8 种包装数据类型。我们知道 Java 是一种面向对象编程的高级语言,所以包装类型正是为了解决基本数据类型无法面向对象编程所提供的。
我们首先来看看以下代码,例1:
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = new Integer(100);
Integer i4 = new Integer(100);
System.out.println(i1 == i2);//true
System.out.println(i1 == i3);//false
System.out.println(i3 == i4);//false
System.out.println(i1.equals(i2));//true
System.out.println(i1.equals(i3));//true
System.out.println(i3.equals(i4));//true
}
当我们修改了值为200的时候,例2:
public static void main(String[] args) {
Integer i1 = 200;
Integer i2 = 200;
Integer i3 = new Integer(200);
Integer i4 = new Integer(200);
System.out.println(i1 == i2);//false
System.out.println(i1 == i3);//false
System.out.println(i3 == i4);//false
System.out.println(i1.equals(i2));//true
System.out.println(i1.equals(i3));//true
System.out.println(i3.equals(i4));//true
}
通过上面两端代码,我们发现修改了值,第5行代码的执行结果竟然发生了改变,为什么呢?首先,我们需要明确第1行和第2行代码实际上是实现了自动装箱的过程,也就是自动实现了 Integer.valueOf 方法,其次,==比较的是地址,而 equals 比较的是值(这里的 eauals 重写了,所以比较的是具体的值),所以显然最后五行代码的执行结果没有什么疑惑的。既然==比较的是地址,例1的第5行代码为什么会是true呢,这就需要我们去了解包装类的缓存机制。
其实看Integer类的源码我们可以发现在第780行有一个私有的静态内部类,如下:
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 加载的时候就已经加载完成了,以上代码初始化了一个 Integer 类型的叫 cache 的数组,取值范围是[-128, 127]。缓存机制的作用就是提前实例化相应范围数值的包装类对象,只要创建处于缓存范围的对象,就使用已实例好的对象。从而避免重复创建多个相同的包装类对象,提高了使用效率。如果我们用的对象范围在[-128, 127]之内,就直接去静态区找对应的对象,如果用的对象的范围超过了这个范围,会帮我们创建一个新的 Integer 对象,其实下面的源代码就是这个意思:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
所以 例1 代码里,i1 和i2 是100,值的范围在[-128, 127],所以直接区静态区找,所以i1和i2指向的地址是同一个,所以 i1==i2;而在例2的代码里,i1 和i2 是200,值的范围不在在[-128, 127],所以分别创建了一个新的对象,放在了堆内存里,各自指向了不同的地址,所以地址都不同了,自然 i1 不等于 i2。
通过分析源码我们可以发现,只有 double 和 float 的自动装箱代码没有使用缓存,每次都是 new 新的对象,其它的6种基本类型都使用了缓存策略。 使用缓存策略是因为,缓存的这些对象都是经常使用到的(如字符、-128至127之间的数字),防止每次自动装箱都创建一次对象的实例。
提枪策马乘胜追击04-21 20:01
代码小兵92504-17 16:07
代码小兵98804-25 13:57
杨晶珍05-11 14:54