Integer使用问题

356 阅读3分钟

隐式转换问题

什么情况下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)

\