[jjzhu学java]之自动装箱的陷阱

来源:互联网 发布:斗鱼主播直播软件 编辑:程序博客网 时间:2024/05/18 10:56

自动装箱、拆箱的陷阱

装箱与拆箱

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));    }}

输出的结果是

truefalsetruetruetruefalse

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语法糖提供的自动装箱和拆箱过程,可以通过编译->反编译来看其具体是如何进行装箱和拆箱的,然后对其操作进行源码查看,进一步了解其装、拆过程。

0 0
原创粉丝点击