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

  1. 云栖社区>
  2. 博客>
  3. 正文

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

zhujiajunup 2016-10-31 12:47:00 浏览1566
展开阅读全文

自动装箱、拆箱的陷阱

装箱与拆箱

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

网友评论

登录后评论
0/500
评论
zhujiajunup
+ 关注