是五月呀!

java包装类

1. 自动装箱与自动拆箱

那我们来分析Integer i = 5;的过程;
在jdk1.5以前,这样的代码是错误的,必须要通过Integer i = new Integer(5);这样的语句实现;
而在jdk1.5以后,Java提供了自动装箱的功能,只需Integer i = 5;这样的语句就能实现基本数据类型传给其包装类,JVM为我们执行了Integer i = Integer.valueOf(5);

相对应的,把基本数据从对应包装类中取出的过程就是拆箱,如

1
2
Integer i = 5;
int j = i; // 这样的过程就是自动拆箱

源码方面,装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的xxxValue方法实现的。(xxx代表对应的基本数据类型)

2. 比较大小

2.1 整型

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testInteger() {
Integer n1 = 1;
Integer n2 = 1;
Integer n3 = 200;
Integer n4 = 200;
assertTrue(n1 == n2);
assertTrue(n1.equals(n2));
assertFalse(n3 == n4);
assertTrue(n3.equals(n4));
}

对于Integer,需要注意它的valueOf方法:

1
2
3
4
5
6
7
8
/**
* This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

这里引入了IntegerCache,缓存-128到127之间的常用数值,注意,范围外的数值也有可能被缓存。
即在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
上面代码中,n1和n2的数值都为1,在缓存范围内,所以引用了同一个对象。

类似的,Byte、Character、Short、Integer、Long这几个整型包装类都会缓存[-128,127]之间的数值。

2.2 浮点型

与整型不同,浮点型包装类Float和Double的valueOf并不会缓存常用值,而是直接新建对象:

1
2
3
public static Double valueOf(double d) {
return new Double(d);
}

2.3 布尔型

1
2
3
4
5
6
7
8
9
@Test
public void testBoolean() {
Boolean b1 = true;
Boolean b2 = true;
Boolean b3 = false;
Boolean b4 = false;
assertTrue(b1 == b2);
assertTrue(b3 == b4);
}

接下来看布尔类型包装类Boolean的valueOf方法:

1
2
3
4
5
6
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

可以看出来,布尔类型的自动装箱会返回两个静态常量值中的一个。

2.4 类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testCompute() {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Long g = 3L;
Long h = 2L;
assertTrue(c == (a + b)); // (1)
assertTrue(c.equals(a + b)); // (2)
assertTrue(g == (a + b)); // (3)
assertFalse(g.equals(a + b)); // (4)
assertTrue(g.equals(a + h)); // (5)
}

a + b 运算符操作,先进行拆箱,计算得到int型的3,然后在进行封箱操作,传入equals方法。

这里面需要注意的是:

  • 当 “==” 运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
  • 对于包装器类型,equals方法并不会进行类型转换。

重点看(3)(4)(5)。
(1)和(3)由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。
(2)(4)(5)对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法。不同类型进行equals方法比较肯定不相等。

3. 空指针

1
2
3
4
5
6
7
@Test(expected = NullPointerException.class)
public void testNPE() {
Integer a = null;
if (a > 0) { // NPE
System.out.println(a);
}
}

在执行a > 0时,需要对a进行拆箱操作,也就是调用a的intValue方法,而a为空,则抛出NPE。
因此,对于包装类,需要先进行判空检验。