Java学习之旅第二季-10:包装类
10.1 何为包装类
我们已经了解到基本类型和引用类型之间无法进行赋值操作,甚至无法将基本数据类型值与对象引用进行比较。Java API中的某些部分仅能处理对象,例如,Java 中的集合仅能处理对象。我们无法创建包含基本数据类型值(如 1、2、3 和 4)的List。在将这些基本数据类型值存储到列表或集合中之前,就需要将它们封装成对象。
基本数据类型值与引用数据类型值之间的赋值不兼容的问题一致就存在于Java中。所以 Java API 在 java.lang 包中提供了八个类来表示八种基本类型中的某一种。这些类被称为包装类(Wrapper Class),因为它们会将一个基本数据类型的值封装起来,除此之外,Java 对其还有特殊的支持。
下表是基本数据类型与其对应包装类的对应关系,处理 int 和 char之外,其它类型的包装类名称只是将首字母改大写:
基本数据类型 | 包装类 |
---|---|
byte | Byte |
int | Integer |
short | Short |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
本小节只关注包装类作为数据类型的使用,而不过多关注它们提供的方法、
10.2 包装类的实例化
所有的包装类都是不可变的。它们提供了三种创建其对象的方法:
- 使用构造方法(已弃用),自 Java 9 开始被标记为 Deprecated
- 使用 valueOf() 工厂静态方法,有些包装类有两种重载形式,一个参数是基本数据类型,另一个字符串
- 使用 parseXxx() 方法,其中 Xxx 是包装类的名称。该方法在 Character 类中不可用。
示例:
Integer num1 = new Integer(10);
Integer num2 = Integer.valueOf(10);
Integer num3 = Integer.valueOf("10");
Integer num4 = Integer.parseInt("10");Float num5 = Float.valueOf(1.23F);
Float num6 = Float.valueOf("1.23F");
Float num7 = Float.parseFloat("1.23F");
Double num8 = Double.valueOf(1.23);
Double num9 = Double.valueOf("1.23");
Double num10 = Double.valueOf("1.23");Character c1=Character.valueOf('a');Boolean b1 = Boolean.valueOf("true");
Boolean b2 = Boolean.parseBoolean("false");
有意思的一点是调用 Integer、Byte、Short 和 Long 的 valueOf 方法时,在内部会缓存一个对象,以Integer为例:
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);
}
上面的方法声明是Integer类提供的,意思是传入的参数在 [-128~127] 之间时,会从缓存数组中获取对象,超出这个范围则创建新的对象。我们可以使用 == 运算符测试一下-
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // 输出 true
如果将上面变量a 和 b 的值改大一点,情况就不同了:
Integer a = Integer.valueOf(128);
Integer b = Integer.valueOf(128);
System.out.println(a == b); // 输出 false
因为 == 运算符用于引用数据类型之间的比较时,实际比较的是地址是否相同,即两个对象是不是同一个。所以从刚才的测试结果来讲,当把要传入 valueOf 方法的整型参数从127改为128时,返回的Integer实例就不是同一个了。其它的数据我就会不再测试了。大家可以自行测试,看范围是不是[-128~127]之间就是统一对象。
10.3 自动装箱与自动拆箱
所谓装箱(box)就是基本数据类型转换成对应的包装类;反之拆箱(unbox)是指包装类转换为对应的基本数据类型。
上面对包装类的实例化中,很多方法可以将基本数据类型数据转换为对应包装类实例,也就实现了装箱操作。如果要实现拆箱,也可以调用包装类提供的方法,下面以整型数据类型数据为例:
Integer num1 = Integer.valueOf(10); // 装箱
int num2 = num1.intValue(); // 拆箱。 num2 的值是 10
从Java 5 开始,就引入了自动装箱与自动拆箱机制,用于在基本数据类型与对应的包装类之间自动互相转换,且该转换是在编译时执行,实际上编译之后是调用的静态方法valueOf。此时的代码就简洁很多了、
Integer num1 = 10;
int num2 = num1;
可以看到,基本数据类型与对应的包装类型之间可以自动进行转换,无需调用任何其他方法。
再论比较运算符
之前介绍比较运算符时提到,基本数据类型与引用数据类型之间不能进行比较,但是包装类除外。另外包装类之间也可以进行大小比较,此时会自动进行拆箱操作:
Integer num1 = Integer.valueOf(12);
System.out.println(num1 > 10); // 输出 true
Integer num2 = 13;
System.out.println(num1 > num2); // 输出 false
另外若包装类的实例值是自动装箱获得的,比较时需考虑缓存,例如:Integer,其范围默认是:[-128~127]
Integer num1 = 127;
Integer num2 = 127;
System.out.println(num1 == num2); // 输出 trueInteger num3 = 128;
Integer num4 = 128;
System.out.println(num3 == num4); // 输出 false
再论算术运算符
当有了自动拆箱之后,之前介绍到的算术运算符也有了新的使用场景:
Integer num3 = 1;
Integer num4 = 2;System.out.println(num3 + num4); // 3
System.out.println(num3 - num4); // -1
System.out.println(num3 * num4); // 2
System.out.println(num3 / num4); // 0
System.out.println(num3 * num4); // 2
再论方法重载
如果有以下的两个重载方法,一个参数是int类型,另一个参数是Integer类型:
public class BoxUnboxDemo {public void print(int num) {System.out.println("print(int num)");}public void print(Integer num) {System.out.println("print(Integer num)");}
}
当使用如下实参调用时,会访问哪一个呢?结论应该很容易得出,会找形参与实参类型最匹配的方法:
BoxUnboxDemo demo=new BoxUnboxDemo();
demo.print(1); // 输出 print(int num)
demo.print(Integer.valueOf(1)); // print(Integer num)
假如只声明了一个方法呢?比如:
public class BoxUnboxDemo {public void print(int num) {System.out.println("print(int num)");}
}
此时两句调用都能成功吗?结果如下:
print(int num)
print(int num)
如果只保留另一个方法,
public class BoxUnboxDemo {public void print(Integer num) {System.out.println("print(Integer num)");}
}
调用代码不变,结果如下:
print(Integer num)
print(Integer num)
结论已经显而易见了,在没有找到形参与实参类型最匹配的方法时,会进行自动拆箱或装箱,然后找合适的方法调用。
null问题
在自动装箱与自动拆箱时,null有时会造成潜在的bug
Integer n = null; // n 被赋值为 null
int a = n; // 运行时抛出空指针异常 NullPointerException
上面两句代码在编译时并不会出现语法错误,但是当程序运行起来后,会有异常抛出,这是因为 null 对象没有办法通过内部转换为对应的基本数据类型。
10.4 小结
本小节介绍了Java中的包装类,主要包含包装类的作用、8种基本类型对应的包装类及其实例化方式(构造方法、valueOf()、parseXxx()),特别说明Integer等类会对[-128,127]范围内的值进行缓存;自动装箱与拆箱机制:Java5开始支持基本类型与包装类之间的自动转换,并详细说明了在比较运算、算术运算和方法重载中的具体表现;另外指出null值在自动拆箱时会导致空指针异常,以及缓存机制对对象比较的影响。