对象包装器与自动装箱
Java是一门非常纯粹的面向对象的编程语言,其设计理念是“一切皆对象”。**但8种基本数据类型却不具备对象的特性。**据说Java之所以提供8种基本数据类型,主要是为了照顾程序员的传统习惯。这8种基本数据类型的确带来了一定的方便性,但在某些时候也会受到一些制约。比如,所有的引用类型的变量都继承于Object类,都可以当做Object类型的变量使用,但基本数据类型却不可以。
如果某个方法需要Object类型的参数
,但实际需要传入的值却是数字的话,就需要做特殊的处理了。
这时我们需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。
eg:
Integer
类对应基本类型为
int
。通常,这些与基本类型对应的类称为包装器(wrapper)。
包装器是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。
同时,包装器类还是
final
,因此不能派生它们的子类。
假设我们想要定义一个整型数组列表。但是尖括号中的类型参数不允许是基本类型,
ArrayList<int>
是不允许的。这时我们就可以用到Integer包装器类。ArrayList<> list = new ArrayList<Integer>();
这样,我们在向list中添加数据时,就应该添加
Integer
对象,但是,Java中有一个很有用的特性,
可以很简单的向
ArrayList<Integer>
中添加int类型的元素
;
让
list.add(3);
自动的变换成
list.add(Integer.valueOf(3));
这种变换称为
自动装箱(autoboxing)
“装箱”(boxing)这个词来源于C#
相反的,当将一个
Integer
对象赋给一个int值时,将会
自动的拆箱。
也就是说,
编译器
将以下语句:
int n = list.get(i)
转换成:
int n = list.get(i).intValue();
自动的装箱与拆箱也适用于算术表达式。
例如自增运算符:
Integer n = 3;
n++; // 编译器自动插入一条对象拆箱的指令,运算完成后再装箱。
大多数情况下容易有一种假象,认为基本类型与它们的对象包装器是一样的。但它们有一点有很大不同:同一性。
== 运算符运用于对象时检测的是对象是否有相同的内存位置。
- 不同包装类不能直接进行比较不能用==进行直接比较。
-
不能使用
compareTo
方法进行比较,虽然它们都有
compareTo
方法,但该方法只能对相同类型进行比较
所以
Integer a = 1000;
Integer b = 1000;
if(a == b)...
//此语句通常会失败;
自动装箱规范要求
boolean、byte、char <= 127
介于-128和127之间的short和int被包装到固定的对象中。
即如果在此范围内的short/int值的==运算比较结果一定成功。
自动装箱注意的点:
-
包装器类引用可以为
null
,若没有值,则可能会
NullPointerException
Integer n = null; System.out.println(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
-
装箱和拆箱是编译器要做的工作,而不是虚拟机。
编译器在生成类的字节码文件时就会插入必要的方法调用(自动的拆箱和装箱),虚拟机只是执行这些代码。
使用数值包装器通常还有一个原因:
可以将某些基本方法放在包装器中,会很方便
—> API文档。
// 将一个数字字符串转换成数值
int x = Integer.parseInt(s);
// 这里,parseInt与Integer对象没关系,它是一个static静态方法(工具方法),只是Integer类是一个放它的好地方。
其它数值类也实现了一些有用的方法。
Warning:
有些人认为包装器类可以用来实现修改数值参数的方法,然而这是错误的。
Java方法的参数总是按值传递,所以不可能编写一个能够增加整型参数值的Java方法。
因为Integer对象是
不可变的
,包含在包装器中的内容不会改变。不能使用这些包装器类创建会修改数值参数的方法。如果确实向别写一个修改数值参数值的方法,可以使用
org.omg.CORBA
包中定义的某个持有者(holder)类型,包括
IntHolder
、
BooleanHolder
等。