问题引入
这两天在工作过程中,被同事提醒我的Integer类等值判断最好用equals判断,加上之前刚遇到过一个Long类型的相同数字使用 == 判断时返回false,不禁让我思考,为什么不可以用 == 号,之前用的时候为啥没遇到这种情况?Object对象的equals方法不也是比较的地址值吗?带着这些问题,我去翻了翻源码,结果发现,和我之前想的确实不一样。
结论
先说结论,通过自动装箱得到的包装类中,Integer、Short、Byte、Long在值为-128~127的范围内是可以用 == 判断的,其中Integer的最大范围可以通过设置比127大;Character在0~127的范围是可以用 == 判断的;Boolean可以用 == 判断;Double和Float不可以用==判断。在工作学习中,最好养成用equals进行等值比较的习惯!
为什么会有部分值可以用 == 判断的情况
首先我们要知道通过自动装箱的包装类是怎么得到的。
我们都知道,java代码有些内容我们不写,系统在编译的时候是会自动帮我们加上的,就比如一个对象,不写构造方法的时候,系统会默认帮我们加上一个无参构造的语句。其实自动装箱同理,我们可以写成 Short i = 1; 的形式,但是在编译的时候,系统是调用的是Short.valueOf()方法得到的Short对象。例如
Short s1 = Short.valueOf((short)1); Short s2 = Short.valueOf((short)1); System.out.println(s1 == s2);
那么进去valueOf方法看看,应该就可以知道为什么会有部分值可以 == 判断了吧!
Integer首先看一下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);
}
这里先是一个判断,如果i在一个区间范围内,就返回一个数组里的内容,否则的话就new一个新的对象出来。那么,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() {}
}
通过查找可以发现,IntegerCache是Integer的一个静态内部类。有三个属性,分别是最低值,最高值,Integer数组;除此之外就只有一个静态代码块了。在静态代码块中一共做了三件事:1、给high属性赋值;2、创建一个Integer的数组,长度刚好是最大值到最小值的个数;3、将最小值和最大值区间的值依次new一个Integer对象存进数组中。
其中high属性的赋值并不是直接就赋值了,而是做了一个判断,从一个地方取值,如果有值,和127比较取最大值,再与Integer的最大值(0x7fffffff)+128-1进行比较,取一个最小值赋给high属性。所以Integer的最大缓存值才可以自己设定。也不是没有边界的,最大不超过(Integer的最大值(0x7fffffff)+128-1),最小不低于127(后面的断言有做限制,如果小于127会报error )。
看着这就明白了,这就是将区间内的值放到数组中缓存起来。再联系前面valueOf的代码,就顿悟了,原来它是先判断int值是否在缓存的值的区间内,如果是的话,就直接从缓存数组中取出返回,如果不是就自己new一个新对象。
所以说,以前自己在练习的时候都是用的100以内的数字,难怪会用 == 判断成功,他们返回的都是一个对象,比较地址值当然成功啦。
Byte
接着来看一下Byte的valueOf()方法
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
这个就更简单粗暴啦,直接返回ByteCache的缓存数组里的对象。那好,接着去看ByteCache里都有什么!
private static class ByteCache {
private ByteCache(){}
static final Byte cache[] = new Byte[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}
我们都知道,byte的取值范围就是-128~127。所以这里面就直接将这些值存进了缓存数组中。所以不管你是Byte的什么值,通过valueOf()方法得到的,只要数字相同,他们的地址值也一定相同。
Short话不多说上代码
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
private static class ShortCache {
private ShortCache(){}
static final Short cache[] = new Short[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}
可以看到ShortCache和ByteCache都差不多的内容,也是for循环缓存-128~127之间的值的对象。如果在缓存区间,就从缓存数组里取,不同才重新new一个对象出来。
Long酒喝干再斟满,再看源码不嫌烦!
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
还是同样的配方,还是同样的味道,依旧是缓存-128~127之间的值的对象,在调用valueOf()方法的时候,还是判断,在区间内就从缓存数组中取对象,不在就重新new一个出来。
Character当你看到这个的时候,就证明你已经看了一半了!
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
可以看到,和前面三个基本上不会有太大的差别,唯一的区别就是缓存的0~127的值
Boolean前面的cache看吐了吗,别慌,新鲜的来了~
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
咦,终于不是缓存里取了,那它是从哪拿的呢,点开可以发现,其实都差不多,因为boolean只有两个值,所以不搞数组啦,直接声明两个静态常量好啦!
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
所以,还是的,不管你通过valueOf()方法拿多少个对象, 最终指向的都是这两个。那 == 比较肯定行啊!
Double和Float喂?我不要面子的?前面兄弟们都单独讲,到我俩就没牌面了?
public static Double valueOf(double d) {
return new Double(d);
}
public static Float valueOf(float f) {
return new Float(f);
}
确实,因为他俩既简单又相似,就干脆放一块好了。可以看出,他俩是直接就new对象的,所以自动装箱时,这两个万万不可用 == 进行比较,一定是不同的对象嘛!
如何等值判断---equals前面划了那么多,大家都知道在装箱的时候他们是如何产生的吧。装箱用 == 都会有问题,那直接new 出来的对象就更不可能用 == 进行判断啦。那咋办嘛,祈祷equals被重写了呗!
他们的equals判断都是重写过的方法,而且基本上不会有很大差别,这里就只挑一个进行说了
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
都是有判断传入的对象是否是包装类的类型,然后再去判断具体的值是否相等。
好啦,以上就是本次分享的全部内容了。如有问题,欢迎指正!



