1. 对象包装器
某些情况下,基本类型如 int
需要转换为对象。
因此,在 Java 中,所有的基本类型都有一个与之对应的类,通常,这些类被称为包装器。
对象包装器类是不可变的,也就是说,一旦构造了包装器,就不允许改变包装在其中的值。
同时包装器类还是 final
,因此,不能定义包装器类的字类。
注意:由于包装器类引用可以为 null
, 因此自动装箱可能会抛出 NullPointException
异常:
Integer n = null;
System.out.println(2 * n); // Throws NullPointerException
2. 自动装箱与拆箱
2.1 什么是自动装箱与拆箱?
自动装箱:将一个基本类型赋值给一个对应的包装器对象。
Integer number = 1;
编译器会自动将上述语句转换为:
Integer number = Integer.valueOf(1);
自动拆箱:将一个包装器对象付给一个对应的基本类型。
int n = number;
编译器会自动将上述语句转换为:
int n = number.intValue();
在算术表达式中,出现基本数据类型与包装器对象进行运算时,编译器会自动插入对象拆箱的指令,对基础数据类型计算后,再将结果装箱。
此时,表达式中出现混用 Integer
和 Double
类型,Intege
r 值会拆箱,提升为 Double
,再封箱为 Double
:
Integer n = 1;
Double d = 2.0;
System.out.println(true ? n : d); // 1.0
注意
-
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,会插入必要的方法调用。虚拟机只是执行这些字节码。
-
自动拆箱陷阱:注意包装器对象是否为 null。
Integer i = null; int n = i; // Throws NullPointerException
由于
i
为对象,可以指向null
,但运行时,会对i
进行拆箱,也就是对一个空对象执行intValue()
方法,因此会抛出空指针异常。
2.2 比较问题
2.1 包装器对象之间的比较
基于对象对比机制可知,==
检测的实际是对象是否指向同一块存储区域,因此,不同对象之间的比较一般不相等。
而包装器类的特殊实现,会导致不同对象指向同一块存储区域,从而出现以下情况。
Integer a = 1;
Integer b = 1;
System.out.println(a == b); // true
Integer c = 1000;
Integer d = 1000;
System.out.println(c == d); // false
为什么会出现这种情况,根据自动装箱可知
Integer a = 1;
实际执行了
Integer a = Integer.valueOf(1);
查看 Integer
源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
根据源码发现,传入的 int
值 在一定范围内是不会自动创建的,而是一个 IntegerCache
类的 cache
数组属性,因此,在一定范围内,Integer
包装器的对象返回的是同一个 Integer
对象,此时,==
成立。
查看 IntegerCache
源码:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
VM.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
根据源码可知,值为-128 ~ 127
的 Integer
包装器对象引用的是同一缓存数字中的对象,因此,它们是相等的。
其他的包装器类也有类似的缓存操作,为了避免多次创建对象,事先创造好了一个缓存数组,如果值在这个范围内,就会直接返回实现创建好的对象。
但是,某个范围内的整型数值是有限的,而浮点数不是。
因此, Double
、Float
类型就没有类似的缓存数组。
类型 | 相同对象范围 | 不同对象范围 |
---|---|---|
Integer | -128 ~ 127 | i >= 128 || i < -128 |
Short | -128 ~ 127 | s >=128 || s < -128 |
Character | < 128 | c > 128 |
Long | -128 ~ 127 | l >= 128 || i < -128 |
Byte | 全部 | 无 |
Boolean | 全部 | 无 |
Double | 无 | 全部 |
Float | 无 | 全部 |
2.2 基础类型与包装器类的比较
==
比较运算符:
- 当两个操作数都是包装器类的对象时,比较的是否为同一个对象。
- 当其中一个是表达式时,则比较的是数值(会触发自动拆箱)。
Integer a = 100;
Integer b = 200;
Long c = 300l;
Long d = 200l;
System.out.println(c == (a+b)); // true
System.out.println(c.equals(a+b)); // false
System.out.println(c.equals(a+d)); // true
第一次,先触发自动拆箱,调用 intValue()
方法,比较数值是否相等,因此,结果为true
。
第二次,先触发自动拆箱,调用 intValue()
方法,再进行自动装箱,由于两个都为Integer
类型,自动装箱为 Integer.valueof()
,进行 equals()
比较时,类型不相等,因此,结果为 false
。
第三次,先触发自动拆箱,调用 intValue()
方法,再进行自动装箱,由于其中一个为 Long
类型,自动装箱变为 Long.valueof()
,进行 equals()
比较时,类型相等,数值相等,因此,结果为 true
。