对象包装器与自动装箱

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值的==运算比较结果一定成功。

自动装箱注意的点:

  1. 包装器类引用可以为null,若没有值,则可能会NullPointerException
Integer n = null;
System.out.println(2*n); // throws NullPointerException
  1. 若在一个条件表达式中混合使用IntegerDouble类型,Integer值就会拆箱,提升转换为double,再装箱为Double.
Integer n = 1;
Double x = 2.0;
System.out.println(true? n : x);
// prints 1.0
  1. 装箱和拆箱是编译器要做的工作,而不是虚拟机。
    编译器在生成类的字节码文件时就会插入必要的方法调用(自动的拆箱和装箱),虚拟机只是执行这些代码。

使用数值包装器通常还有一个原因:可以将某些基本方法放在包装器中,会很方便—> API文档。

// 将一个数字字符串转换成数值
int x = Integer.parseInt(s);
// 这里,parseInt与Integer对象没关系,它是一个static静态方法(工具方法),只是Integer类是一个放它的好地方。

其它数值类也实现了一些有用的方法。


Warning:

有些人认为包装器类可以用来实现修改数值参数的方法,然而这是错误的。

Java方法的参数总是按值传递,所以不可能编写一个能够增加整型参数值的Java方法。

因为Integer对象是不可变的,包含在包装器中的内容不会改变。不能使用这些包装器类创建会修改数值参数的方法。

如果确实向别写一个修改数值参数值的方法,可以使用org.omg.CORBA包中定义的某个持有者(holder)类型,包括IntHolderBooleanHolder等。