自动装箱、拆箱的陷阱
装箱与拆箱
java语言中为每种基本数据类型(int,float,double…)都提供了与之对应的包装器类型(Integer,Float,Double)。从java se5之后就开始提供了自动装箱的特性。想要得到一个数值为2016的Integer时,只需要如下的赋值语句:
//Integer a = Integer.valueOf(2016);
Integer a = 2016;
该语句就会自定根据=右边的数值创建相应的Integer,这个过程就是自动装箱。
拆箱与装箱是相对应的,即自动将包装器类型转换为基本类型,如下赋值语句将会触发拆箱操作
int n = a; //n=2016
java提供了这样的语法糖,它到底是怎么实现的呢,我们可以写如下代码进行编译与反编译测试
public class GenericTypes {
public static void main(String[] args) {
Integer a = 2016;
int n = a;
System.out.println(n);
}
}
在该java文件的同级目录下运行如下命令进行编译
javac GenericTypes.java
然后用jd-gui对生成的class文件进行反编译,进过编译->反编译后的代码会和我们之前写的一样吗?事实显示并不一样。
public class GenericTypes
{
public static void main(String[] paramArrayOfString)
{
Integer localInteger = Integer.valueOf(2016);
int i = localInteger.intValue();
System.out.println(i);
}
}
从结果中可以很清楚的看到,赋值语句与之前截然不同,其对a的赋值其实是自动调用了Integer.valueOf()方法,而对n=a这赋值语句自动调用了Integer.intValue(),对于其他包装类型也是如此,自动装箱与拆箱会自动的调用包装类中的valueOf与xxxValue方法。
装箱陷阱
Integer自动装箱陷阱
之前已经演示了包装类型自动装箱与拆箱的原理。这里先看下面的代码,思考一下输出将会是多少,再进行下一步的讨论。代码片段摘自《深入理解java虚拟机:JVM的高级特性与最佳实践》的P274。
public class GenericTypes {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a+b));
System.out.println(c.equals(a+b));
System.out.println(g == (a+b));
System.out.println(g.equals(a+b));
}
}
输出的结果是
true
false
true
true
true
false
why?c == d为true,e == f为什么为false…..
这里我们进行进一步的讨论。之前我们已经讨论过,Integer自动装箱会自动调用Integer.valueOf()方法,我们可以查看其具体实现是怎么样的,其源码如下:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
这里你就会发现,这里有个判断,如果输入的值i在[-128, IntegerCache.high]范围内时,返回的是IntegerCache.cache[i + 128];这个IntegerCache是什么,显而易见,它是“整型缓存”,也就是说Integer类型对[-128, IntegerCache.high]范围内的数字进行了缓存,进一步看IntegerCache类的源代码:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
从代码中可以知道,IntegerCache默认对[-128,127] 用了一个cache数据进行了缓存的,缓存最大值h(默认127)可以配置,也就是说-128-127之间的Integer都是对cache中的Integer包装类型的一个引用,这里就可以解释为什么System.out.println(c == d); 为true,而 System.out.println(e == f); 为false,因为321显然>127。
因为缓存最大值是可以配置的,Integer类中源码如是写道:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. During VM initialization the
* getAndRemoveCacheProperties method may be used to get and remove any system
* properites that configure the cache size. At this time, the size of the
* cache may be controlled by the vm option -XX:AutoBoxCacheMax=<size>.
*/
// value of java.lang.Integer.IntegerCache.high property (obtained during VM init)
private static String integerCacheHighPropValue;
static void getAndRemoveCacheProperties() {
if (!sun.misc.VM.isBooted()) {
Properties props = System.getProperties();
integerCacheHighPropValue =
(String)props.remove("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null)
System.setProperties(props); // remove from system props
}
}
我们可以通过-XX:AutoBoxCacheMax=size来进行对缓存最大值进行配置。这里我们配置为400,重新运行,System.out.println(e == f); 将输出true。
在看看最后一个System.out.println(g.equals(a+b));为什么会输出false,查看源码就立即知道答案:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
包装类的equals只比较同类型的对象。我们对测试代码进行反编译
public class GenericTypes
{
public static void main(String[] paramArrayOfString)
{
Integer localInteger1 = Integer.valueOf(1);
Integer localInteger2 = Integer.valueOf(2);
Integer localInteger3 = Integer.valueOf(3);
Integer localInteger4 = Integer.valueOf(3);
Integer localInteger5 = Integer.valueOf(321);
Integer localInteger6 = Integer.valueOf(321);
Long localLong = Long.valueOf(3L);
System.out.println(localInteger3 == localInteger4);
System.out.println(localInteger5 == localInteger6);
System.out.println(localInteger3.intValue() == localInteger1.intValue() + localInteger2.intValue());
System.out.println(localInteger3.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue())));
System.out.println(localLong.longValue() == localInteger1.intValue() + localInteger2.intValue());
System.out.println(localLong.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue())));
}
}
可以看到,传给equals是基础int类型,自然返回的是false。
其他包装类型
其他的包装类型都可以根据上诉方法进行分析,这里只给出几个测试代码,可以先思考,然后进行实际检测
public static void main(String[] args) {
Boolean b1 = true;
Boolean b2 = true;
System.out.println(b1 == b2);
System.out.println(b1.equals(b2));
Character c1 = 'a';
Character c2 = 'a';
System.out.println(c1 == c2);
Float f1 = 0.1f;
Float f2 = 0.1f;
System.out.println(f1 == f2);
Long l1 = 10L;
Long l2 = 10L;
Long l3 = 210L;
Long l4 = 210L;
System.out.println(l1 == l2);
System.out.println(l3 == l4);
Double a = 10.0;
Double b = 10.0;
System.out.println(a == b);
System.out.println(a.equals(b));
}
总结
java语法糖提供的自动装箱和拆箱过程,可以通过编译->反编译来看其具体是如何进行装箱和拆箱的,然后对其操作进行源码查看,进一步了解其装、拆过程。