最近在编码过程中,代码提交之后sonar报了Bug警告,引发了对java中Integer类的一些思考。代码如下:
public boolean verifyDimensions() {
if (CollectionUtils.isEmpty(dimensions)) {
return false;
} else {
boolean hasRightDim = false;
for (DimensionInApplyDTO dim : dimensions) {
if (dim.getIsRight() == Integer.valueOf(1)) { // 报错位置; 注:dim.getIsRight()返回 0/1/NULL Integer对象
hasRightDim = true;
break;
}
}
if (!hasRightDim)
return false;
}
return true;
}
Bug警告: This method compares two reference values using the == or != operator,
where the correct way to compare instances of this type is generally with the equals() method. It is possible to create distinct instances that are equal but do not compare as == since they are different objects. Examples of classes which should generally not be compared by reference are java.lang.Integer, java.lang.Float, etc.
意思就是两个引用类型的对象进行比较,不要用“==”,要用equals(),因为"=="比较的是地址呀云云... 那为什么这段代码我在测试的时候并没有发现错误呢,后来看了下Integer的源码,结合网友大神的解答,一切都明了了。
Integer与int在拆箱装箱过程中主要用到两个方法: valueOf() 和 intValue(),下面通过具体的例子来说一下拆箱装箱的过程~
1). intValue(): Integer --> int 拆箱
/**
* Returns the value of this {@code Integer} as an
* {@code int}.
*/
public int intValue() {
return value;
}
@Test
public void test1() throws Exception {
int i = 10;
Integer j = 10;
System.out.println(i == j); // true
Integer z = null;
System.out.println(i == z); // java.lang.NullPointerException
}
第6行结果为true: 在Integer对象与int对象i做比较时, Integer j 对象会调用intValue()方法返回一个int对象, 返回的这个int对象再与i进行比较,此时两个做比较的对象都是int对象,都为基本类型,所以“==”直接就比较值是否相等。
第9行结果为空指针异常: z为空对象,所以调用一个空对象的某个方法时,会报空指针异常,我们在比较Integer与int时,注意Integer不能为null。
2). valueOf(): int --> Integer 装箱
@Test
public void test2() throws Exception {
Integer i = Integer.valueOf(127);
Integer j = 127;
System.out.println(i == j); //true
Integer x = Integer.valueOf(128);
Integer y = 128;
System.out.println(x == y); //false
}
测试代码第5行 j的赋值是将int对象赋值给Integer类型,此时int对象会执行自动装箱操作,调用Integer的静态方法valueOf(int i),传入一个int对象,返回一个Integer对象。所以第4行第5行的两个赋值方式是没有区别的。
那么为什么两次比较,第一次比较i,j相等,第二次比较x,y就不相等呢,别急,我们看一下Integer.valueOf(int i)的源码:
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
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为-128, IntegerCache.high为127。 由代码可看出, 如果传入的int值在-128与127之间,则调用了一个IntegerCache类,否则new一个新的Integer对象。
我们都知道new出来的对象位于堆中,new多少次就有多少个不同的对象,但是IntegerCache是什么东西呢?
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. // 第一次使用时进行初始化
* The size of the cache may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
* // 数组的大小可以通过控制虚拟机的-XX:AutoBoxCacheMax=参数来设置。
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[]; // 缓存了一个Integer类型的数组
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源码我们能够知道,IntegerCache这里面封装了一个Integer类型的数组(代码12行),存放-128至127的Integer类型的对象,第一次使用时进行初始化(代码34行)。所以Integer.valueOf(int i)方法,在传入i的值不在-128和127之间时,便new出一个新的Integer对象;如果范围在-128和127之间,则直接从IntegerCache缓存中取出i 对应的Integer对象。
我们转过头来再看下测试代码:
@Test
public void testName2() throws Exception {
Integer i = Integer.valueOf(127);
Integer j = 127;
System.out.println(i == j); //true
Integer x = Integer.valueOf(128);
Integer y = 128;
System.out.println(x == y); //false
}
127在-128和127之间,所以int类型的127在执行装箱操作,调用valueOf方法时,取出的是IntegerCache缓存的Integer对象,所以,不管来多少个127,只要是调用valueOf方法,返回的都是一个对象,用“==”返回true,内存地址也仅此一份。
128不在-128和127之间,每次调用valueOf方法,都会产生一个独一无二的Integer对象,所以用“==”返回false;但是因为两个都是引用对象,用equals()则会返回true,因为x,y的字面值均为128。
回到文章开头,sonar给我的代码报出了一个严重bug,但是我自己在测试时没有发现错误,
if (dim.getIsRight() == Integer.valueOf(1)) ...
是因为在项目逻辑中,dim.getIsRight()只可能返回 null、1、0三个情况, 返回的1也是IntegerCache中的引用对象,所以用“==”,等于1的情况下也会返回true,但是sonar不知道这种情况,提出了bug,所以说,无论什么时候代码质量一定要严谨。
3). parseInt()
@Test
public void testName3() throws Exception {
System.out.println(Integer.parseInt("128") == Integer.valueOf(128)); // true
int i = Integer.parseInt("128");
Integer j =Integer.valueOf(128);
System.out.println(i == j); // true
}
上面这个例子用到了Integer的另一个方法parseInt(), 将一个对象转换为int对象(源码不贴了),
parseInt()返回值是一个int对象,注意是一个int对象,不是Integer!
所以测试代码中 Integer.parseInt("128") 其实就是int类型的128,第4行也就等价于 System.out.println(128 == Integer.valueOf(128)); 后者调用valueOf方法返回一个128的Integer对象,与前面的128比较时,后者自动拆箱成为int类型的128,所以最后两个int类型的128比较,返回true。
关于int与Integer类型各种各样的问题,大家如果有问题可以在评论里发一下,在今后遇到比较好的例子或者刁钻的面试题,我也会继续在本文下面列出,持续更新,共同进步~