对象包装器与自动装箱
有时,需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。
例如,Integer类对应的基本类型int。
通常,这些类称为包装类。
这些包装器类的名字都很明显:Integer, Long, Float, Double, Short, Byte, Character, Boolean
前六个类派生于公共的超类Number。
包装器类是不可变的,也就是说,一旦构造了包装器,就不允许更改包装在其中的值。
同时,包装器类还是final,因此不能派生它们的子类。
如果想要定义一个整型数组列表,尖括号中的参数类型不允许是基本类型,也就是说,不能写成ArrayList<int>
这时候就需要用到包装器类Integer。var list = new ArrayList<Integer>
- 由于每个值分别包装在对象中,所以ArrayList
的效率远低于int[]数组。
所以,只有在程序员操作的方便性比执行效率更重要的时候,才会考虑对较小的集合使用这种构造。
有一个很有用的特性,可以很容易地向ArrayList
下面这个调用list.add(3)
会自动变换为list.add(Integer.valueOf(3));
这种变换称为自动装箱。
相反地,如果把一个Integer对象赋给一个int值时,将会自动地拆箱。
也就是说,编译器会把以下语句int n = list.get(i);
转换成int n = list.get(i).intValue();
自动装箱和拆箱也适用于算术表达式。例如,可以将自增运算符应用于一个包装器引用
Integer n = 3;
n++;
编译器会自动地插入一条对象拆箱的指令,然后进行自增计算,最后再将结果装箱。
由于包装器类引用可以为null,所以自动装箱可能会抛出一个空指针异常。
如果在一个表达式中,混合使用Integer和Double类型,Integer值就会拆箱,提升为double,然后再装箱为Double。
- 装箱和拆箱是编译器的工作,而不是虚拟机。
编译器在生成类的字节码时会插入必要的方法调用。虚拟机只是执行这些字节码。
使用数值包装器还有一个原因,就是可以将某些基本方法放在包装器中,这样会很方便。
例如,将一个数字字符串转换成数值。
int x = Integer.parseInt(s);
// 这里与Integer对象没有任何关系,parseInt是一个静态方法。但Integer类是放置这个方法的一个好地方。