隐式转换问题
什么情况下Integer会隐式转换
当包装类Integer与其它基本类型做算术运算或者比较运算的时候,会对Integer调用其方法Integer.intValue()方法,将包装类转换为基本类型。
隐式转换会造成什么问题
在进行算术运算或者比较运算的时候并不会判断Integer是否为null,如果Integer为null将会变成调用null.intValue()方法,从而造成NLP问题。
Integer a = null;
int b = 1;
int c = a + b;// 空指针异常NullPointerException
字节码:
0: aconst_null
1: astore_1
2: iconst_1
3: istore_2
4: aload_1
5: invokevirtual #2 // Method java/lang/Integer.intValue:()I
8: iload_2
9: iadd
10: istore_3
11: return
可以看到在第5个操作的时候,操作4加载了变量a,然后执行了操作5调用了Integer.intValue方法,导致了空指针。
不使用包装类型
int a = 2;
int b = 1;
int c = a + b;
字节码:
0: iconst_2
1: istore_1
2: iconst_1
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: return
操作4和操作5直接就加载了变量a和b,在操作6的时候直接进行加运算,从而达到了目的。
在编辑器中,如果对基本类型的局部变量不赋初值,会编译报错,而对对象Integer编译期间无法检测出来其是否是个有效的值。
同理,在使用比较运算的时候也会造成空指针异常
Integer a = null;
int b = 1;
boolean c = a > b;
字节码:
0: aconst_null
1: astore_1
2: iconst_1
3: istore_2
4: aload_1
5: invokevirtual #2 // Method java/lang/Integer.intValue:()I
8: iload_2
9: if_icmple 16
12: iconst_1
13: goto 17
16: iconst_0
17: istore_3
18: return
小结
因为包装类型和比较类型的进行运算的时候是能够编译通过的,当包装类型为null的情况会造成NLP。
因此在使用包装类型的时候一定要尽量先做空值判断,尽量避免包装类型和基本类型混用。
如:
public static void main(String[] args) { Integer a = null; doSomething(a); } private static void doSomething(int a) { int b = a + 1; }
这种参数传递也会造成自动拆包,从而发生空指针,因此在使用包装类型的时候,一定要像使用普通对象一样,做好NULL值判断,增加程序的鲁棒性。
比较大小问题
在使用包装类型的时候,还有个很大的问题,就是包装类是有缓存的。
在日常开发中大多数时候使用int的数字都不会太大,而一些频繁使用的数字,用包装类型计算的时候,会频繁创建Integer对象,浪费空间和增加垃圾回收负担。
Integer为了减少频繁的对象创建,会将int范围[-128,127]缓存起来,在使用这个范围内的值的时候,直接从缓存中提取对象。
因此需要特别注意在包装类进行比较的时候如果要比较其值相等,一定要使用equals,否则会造成歧义性。
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a==b);// 结果为 true
Integer a1 = Integer.valueOf(128);
Integer b1 = Integer.valueOf(128);
System.out.println(a1==b1);// 结果为 false
上面第一个条件表达式之所以为true,就是因为它们取的都是缓存的对象,是同一个对象。
源码如下:
java.lang.Integer#valueOf(int)
public static Integer valueOf(int i) {
// 这里的 IntegerCache.low = -128,IntegerCache.high=127
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
而第二表达式,因为值不在缓存中,因此会执行new Integer(i) 生成一个新的Integer对象,而==又是比较地址,因此结果为false。
不过根据上面隐式转换问题,如果我们能够确定Integer对象不为空,那么我们可以利用隐式转换,将Integer包装类型和int基本类型直接做==比较也是能够实现值等判断的。
如:
Integer a1 = Integer.valueOf(128);
int b1 = 128;
System.out.println(a1==b1);
以上都是讨论的==比较,如果使用>,<这种表达式比较呢?
包装类型如果使用逻辑表达式>,<运算会自动进行拆包为基本类型后进行比较。
Integer的内部的compareTo方法也是拆包后比较,因此当调用Integer的compareTo方法的时候,两个对象都不能为null。
小结
包装类型Integer引入了缓存设计,减少了常用数字频繁创建对象,提高了对象复用,降低了垃圾回收压力。但是也引入了==比较错觉,因此在使用包装类型比较的时候最好是使用equals比较。
Integer a1 = Integer.valueOf(128); Integer b1 = Integer.valueOf(128); System.out.println(a1.equals(b1));
当无法确定对象是否为空的时候,最好使用Java提供的工具类Objects.equals判断,该方法会自动调用两个对象进行equals比较,并且会自动做null判断。
Objects.equals(a1,b1)
\