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<Integer,A> map = new HashMap<Integer,A>(); // 往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
Integer会cache -128至127.
对的!