引入
没有前戏,直奔代码,来看下面这段代码的运行结果:
@Test
public void test() {
Integer i = 666;
int j = i + 1;
System.out.println("j = " + j);
}
自然,我们都知道会打印 j = 667,曾经我很好奇,i是Integer对象,属于包装类型,而j是int基础数据类型,他俩怎么会在一起运算呢?直到我扒开Integer的外表,直接看到了他的内涵…
当然我是借助于工具看到程序运行的内涵的,IDEA的
jclasslib Bytecode viewer
这个插件,能够反编译代码,而且,反编译出来的指令,可以直接链接到官网上查看。安装好插件后,如图所示,就可以看反编译后的代码指令了:
上面这段程序的字节码指令:
那么这些都是什么玩意呢?
自动装箱与自动拆箱
自动装箱(auto boxing)和自动拆箱(auto unboxing)是Java 5引入的功能,有了这两个功能,Java在编译阶段,会根据上下文对数据类型自动进行转换,可以保证不同的写法在运行时等价。
自动装箱:将值类型装换成引用类型的过程
自动拆箱:将引用类型转换成值类型的过程
Integer i = 666;
int j = i + 1;
这两行代码就是就体现了自动装箱与自动拆箱。
来看一下代码编译后的字节码指令:
0 sipush 666
3 invokestatic #2 <java/lang/Integer.valueOf>
6 astore_1
7 aload_1
8 invokevirtual #3 <java/lang/Integer.intValue>
11 iconst_1
12 iadd
13 istore_2
14 return
第3行:invokestatic #2 <java/lang/Integer.valueOf>
意思是调用类的静态方法,后面指出了是调用Integer的valueOf
这个静态方法,也就是说在编译阶段Java就自动把装箱转换成了Integer.valueOf
;
第8行:invokevirtual #3 <java/lang/Integer.intValue>
调用类实例的方法,这个拆箱就是说在编译阶段就调用了Integer的intValue
方法。
分别来看一下valueOf
和intValue
这两个方法源码。
valueOf:
/**
* 返回表示指定int值的整数实例。如果不需要新的Integer实例,
* 那么通常应该优先使用该方法,而不是构造函数Integer(int),
* 因为通过缓存经常请求的值,该方法可能会产生更好的空间和时间性能。
* 此方法将始终缓存范围为(-128,127]的值,并可能缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
水越来越深了,这时又跑出来个IntegerCache
!
IntegerCache:
/**
* 缓存支持自动装箱为-128,并根据需要通过JLS 127(含)之间的值的对象标识语义。
* 缓存是在第一次使用初始化的。
* 缓存的大小可以通过-XX:AutoBoxCacheMax=<size>选项设置。
* 在虚拟机初始化的过程中,在系统类sun.misc.VM中设置并保存java.lang.Integer.IntegerCache.high
**/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
这就是所谓的Integer缓存,在虚拟机初始化的过程中,就已经缓存好了(-128,127]之间的数据(自动装箱)。
再看一下intValue()方法:
//...
// The value of the {@code Integer}.
int private final int value;
//...
/**
* 以整型数的形式返回该Integer的值
*/
public int intValue() {
return value;
}
以整型数的形式返回该Integer的值,对应拆箱。
比较两个Integer的值
看代码:
@Test
public void test() {
Integer i1 = 66;
Integer i2 = 66;
System.out.println("66 == 66 ? " + (i1 == i2));
Integer i3 = 666;
Integer i4 = 666;
System.out.println("666 == 666 ? " + (i3 == i4));
}
到这大家肯定都知道一个是true,一个是false:
但是,这是为什么呢?被问到这个丝毫不慌,其实前文已经解释了,Integer里面搞了个IntegerCache这个东西,它默认缓存了(-128,127]之间的数据(可以通过 -XX:AutoBoxCacheMax= 设置),并用数组Integer cache[]
保存起来了,也就是说在(-128,127]之间的数值都是IntegerCache.cache[] 数组中的同一个Integer对象。
66在(-128,127]之间,666大于127了,所以i1 == i2
为true,而i3 == i4
为false。
避坑
那么怎么正确的比较两个Integer的值呢?用equals()!
equals:
/**
* 将此对象与指定对象进行比较。
* 当且仅当参数不为null且为包含与此对象相同整型值的整数对象时,结果为真。
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
哈哈,equals方法比较的是两个对象的整型值,不用考虑是基础类型还是引用类型了,一律转换成int类型再进行比较!
这也就是阿里Java开发手册上说的强制使用equals方法比较整型包装类对象的值: