java 核心技术-12版 卷Ⅰ- 5.4 对象包装器与自动装箱
原文
5.4 对象包装器与自动装箱
有时,需要将 int 这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类,例如,Integer类对应基本类型 int。通常,这些类称为包装器 (wrapper)。这些包装器类有显而易见的名字: Integer、Long、Float、Double、Short、Byte、Character 和 Boolean (前6个类派生于公共超类 Number)。包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类还是 final,因此不能派生它们的子类。
假设想要定义一个整型数组列表。遗憾的是,尖括号中的类型参数不允许是基本类型也就是说,不允许写成ArrayList
var list = new ArrayList
警告:由于每个值分别包装在一个对象中,所以ArrayList
幸运的是,有一个很有用的特性,从而可以很容易地向 ArrayList
list.add(3);
将自动地转换成
list.add(Integer.valueOf(3));
这种转换称为自动装箱(autoboxing)。
注释:你可能认为自动包装(autowrapping) 与包装器更一致,不过“装箱”(boxing)这个词源于 C#。
反过来,当将一个 Integer 对象赋给一个 int 值时,将会自动拆箱(unboxed)。也就是说,编译器将以下语句
int n = list.get(i);
转换成
int n = list.get(i).intValue();
自动装箱和自动拆箱甚至也适用于算术表达式。例如,可以将自增运算符应用于一个包装器引用:
Integer n = 3;
n++;
编译器将自动地插人指令对对象拆箱,然后将结果值增 1,最后再将其装箱。
大多数情况下容易有一种假象,认为基本类型与它们的对象包装器是一样的。但它们有一
点有很大不同:同一性。大家知道,== 运算符可以应用于包装器对象,不过检测的是对象是否有相同的内存位置,因此,下面的比较可能会失败:
Integer a = 1000;Integer b = 1000;if(a=b).
不过,Java 实现可以(如果选择这么做) 将经常出现的值包装到系统的对象中,这样一来,以上比较就可能成功。但这种不确定性并不是我们想要的。解决这个问题的办法是在比较两个包装器对象时调用equals 方法。
注释:自动装箱规范要求boolean、byte、char(≤ 127),介于-128和127之间的 short和int 包装到固定的对象中。例如,在前面的例子中,如果将a和b初始化为 109,那么它们的比较结果一定会成功。
提示:绝对不要依赖包装器对象的同一性。不要用== 比较包装器对象,也不要将包装器对象作为锁(参见第 12章)。
不要使用包装器类构造器,它们已被弃用,并将被完全删除。例如,可以使用 Integer.value0f(1000),而绝对不要使用 new Integer(1000)。或者,可以依赖自动装箱: Integer a=1000。
关于自动装箱还有几点需要说明。首先,由于包装器类引用可以为 null,所以自动装箱有可能会抛出一个NullPointerException 异常:
Integer n = null;
System.out.printIn(2 * n); // throws NullPointerException
另外,如果在一个条件表达式中混合使用 Integer 和 Double类型,则Integer 值就会拆箱提升为 double,再装箱为 Double:
Integer n = 1 ;
Double x = 2.0;
System.out.println(true ? n : x); // prints 1.0
最后强调一下,装箱和拆箱是编译器要做的工作,而不是虚拟机。编译器生成类的字节码时会插入必要的方法调用。虚拟机只是执行这些字节码。
注释: Java 将来的版本可能允许类似基本类型的用户自定义类型,其值并不存储在对象中。例如,基本类型Point 的值(包含 double字段x和y) 只是内存中一个16字节的块,并且有两个相邻的 double 值。可以复制这个值,但不能有它的引用。
如果需要一个引用,可以使用一个自动生成的伴随类(在当前提案中,这个类名为 Point.ref)。装箱和拆箱是自动的,这与当前的基本类型相同。
将来某个时候,基本包装器类将与那些类统一起来。例如,Double 将是 double.ref的一个别名。
使用数值包装器通常还有一个原因。Java 设计者发现,可以将某些基本方法放在包装器这会很方便,例如将一个数字字符串转换成数值
要想将字符串转换成整型,可以使用下面这条语句:
int x = Integer.parseInt(s);
这里与Integer 对象没有任何关系,parseInt 是一个静态方法。但 Integer 类是放置这个方法的一个好地方。API注释展示了 Integer 类中一些比较重要的方法。其他数值类也实现了相应的方法。
警告:有些人认为包装器类可以用来实现能修改数值参数的方法,不过这是错误的,在第4章中曾经讲到,由于 Java 方法的参数总是按值传递的,所以不可能编写一个能够让整型参数自增的 Java 方法。
public static void triple(int x) // won't work
{
x=3*x; // modifies local variable
}
将 int 替换成 Integer 能解决这个问题吗?
public static void triple(Integer x) // won't work
{...}
问题在于 Integer 对象是不可变的:包含在包装器中的信息不会改变。所以,不能使用这些包装器类来创建修改数值参数的方法。
API java.lang.Integer jdk1.0
- int intValue() 将这个 Integer 对象的值作为一个int 返回(覆盖 Number 类中的 intValue 方法).
- static String toString(int i) 返回一个新的 String 对象,表示指定数值 的十进制表示
- static String toString(int i, int radix) 返回数值i的一个表示(采用 radix 参数指定的进制)。
- static int parseInt(String s)
- static int parseInt(String s, int radix) 返回一个整数,其数位包含在字符串 s 中。指定字符串必须表示一个十进制整数(第一种方法),或者采用 radix 参数指定的进制(第二种方法)。
- static Integer value0f(String s)
- static Integer value0f(String s, int radix) 返回一个新的 Integer 对象,初始化为一个整数,其数位包含在字符串 s 中。指定字符申必须表示一个十进制整数(第一种方法),或者采用 radix 参数指定的进制(第二种方法)
java.text.NumberFormat 1.1
- Number parse(String s) 返回一个数值,假设给定的 String 表示一个数