Java中的Auto Box/Unbox对写代码带来了便利性,但也挺容易就踩进“坑”里,主要是NPE,对象比较还有内存浪费。
自动装箱、拆箱
先解释下什么是自动装箱、拆箱,基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。
这是最典型的Auto Box/Unbox的代码:
Integer i = 100;
int j = i;
一般我们要创建一个类的对象实例的时候,我们会这样:Class a = new Class(parameter);
当我们创建一个Integer对象时,却可以这样:Integer i = 100; (注意:不是 int i = 100; )
实际上,执行上面那句代码的时候,系统为我们执行了:Integer i = Integer.valueOf(100);
此即基本数据类型的自动装箱功能。自动拆箱则与其相反,int t = i; //拆箱,实际上执行了int t = i.intValue();
注:在运算时,也可以自动拆箱(System.out.println(i++);//i是Integer类型)
易入坑
Auto Box/Unbox在有些场景下容易产生NPE,例如假设有一个这样的方法:
public void execute(int code){}
假设调用方是从一个Map或其他地方获取到的Integer,如果忘记判断是否为null,直接传给execute的方法,就有可能导致NPE。
下面这个关于Integer的Case也是比较常见的:
Integer i = 100;
Integer j = 100;
Integer m = 200;
Integer n = 200;
System.out.println(i == j);
System.out.println(m == n);
其执行结果为:true,false
原因是Integer会cache -127~127的Integer对象,而不在这个范围的则会每次new Integer。
在JavaOne 2010大会上,还有一段关于Auto Box/Unbox带来的频繁YGC的案例代码,代码的关键信息如下:
public class A{
private int code;
public A(int code){
this.code = code;
}
public int get(){
return code;
}
}
public class Demo{
public statice void main(String[] args) throws Exception{
Map map = new HashMap();
// 往map里放1w个从1开始的A对象
...
while(true){
Collection values = map.values();
for(A a: values){
if(!map.containsKey(a.getId())){
// 不可能发生,所以可以随便打点什么
}
Thread.sleep(1);
}
}
}
}
在上面的代码中,其实只需要把A中的private int code和public int get中的int改为Integer,就可以避免频繁的YGC,因此在写代码时还是要稍微注意下Auto Box/Unbox带来的影响。
例子
通过下面的代码例子说明包装类:
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}
看下输出结果:
true
false
true
true
true
false
true
当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。
第一个和第二个输出结果没有什么疑问。第三句由于 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。
相关函数
通过学习Integer肯定要对以下三个函数产生困惑,valueof、parseInt、intValue,下面进行一一介绍
Integer.valueof()返回的是Integer的对象
初始化为指定 String /Int值的新的 Integer 对象。若该 String 不能作为 int 分析,则抛出异常。自动包装调用的函数,注意返回值是Integer
Integer.parseInt() 返回的是一个int的值
它的作用就是将形参 s 转化为整数,注意返回值是int
Integer.valueof().intValue();返回的也是一个int的值。
自动拆包调用的函数,注意返回值是int