一、问题出现
我们都知道,在Java代码中,对于基本类型的值比较可以使用"==",比如下面的代码:
int a = 10, b = 10;
// true
log.info("value is {}", a == b);
long c = 12L, d = 21L;
// true
log.info("value is {}", a == b);
而对于非基本类型而言,特别是对象,我们在使用“==”则是比较的它们在内存中的地址,而非它们的值,比如:
String a = new String("a"), b = new String("a");
// false
log.info("value is {}", a == b);
如果是包装类型呢,int对应的Integer,long对应的Long,它们应该也属于对象范畴,会发生什么呢?
Integer a = 100, b = 100, c = 151, d = 151;
// true
log.info("value is {}",a == b);
// false
log.info("value is {}", c == d);
Long e = 121L,f = 121L,g = 163L,h = 163L;
// true
log.info("value is {}",e == f);
// false
log.info("value is {}", g == h);
结果如上,是不是感觉不可思议?为什么会出现这样的结果呢?
二、源码解析
我们以Integer的源码为例,当我们声明一个Integer类型的实例时,会调用Integer中的valueOf方法进行创建:
/**
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
我们结合注释和源码就可以知晓,倘若Integer实例化的数值是[-128,127]之间的,那么就会使用已经提前缓存下来的对象实例,不会额外去创建新的该值实例;而如果Integer实例化的数值不在这个区间,那么每次实例化的值都是通过新建一个对象来完成的。
所以我们就可以解释如上代码示例中,a和b,e和f,都是在这个范围区间的,它们都是代表缓存中的该值的实例对象,指向的都是该对象的内存地址空间,所以才有a=b,e=f;而c和d,e和f则分别是不同的对象,它们的内存地址空间当然是不同的。
这个问题搞清楚了,那么为什么要缓存[-128,127]这个区间呢,而不是别的区间?
我们再来看看IntegerCache的源码:
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() {}
}
根据JLS规范,[-128,127]是编程中比较常用到的数值,所以对其进行缓存,这是一种空间换时间的策略。如果开发者认为最大值偏大或者偏小,可以自行对虚拟机设置参数进行调节,使得缓存的数值更契合自己的使用场景。
所以说,我们在上面使用的包装类示例中的运行结果并不是一直都是那样的,只有在默认情况下才是那样。
三、解决方法
上面我们只是以包装类Integer的源码为例子,其实其它的包装类型比如Long、Short、Character,查看它们的源码发现,缓存的方法和范围略有不同,但是原理都一样的。
我们在日常开发中,这就是一个大坑,并不能保证所有开发人员都知道这个,所以才规定,对于对象,包括包装类型,如果要比较它们值的大小是否相等,必须使用equals
方法,禁止使用==
。
网友评论