了解JAVA中的装箱与拆箱

1.什么是装箱与拆箱、自动装箱与自动拆箱?

Java为每种基本数据类型都提供了对应的包装器类型,int对应Integer、boolean对应Boolean。。。

所以,装箱就是将基本数据类型转换为包装器类型;拆箱就是将包装器类型转换为基本数据类型。那自动装箱与自动拆箱也很好理解了,就是我们不用在代码里面显式的写装箱与拆箱的代码,由编译器帮我们完成这一操作。

在Java SE5之前

Integer i = new Integer(1); // 装箱
int j = i.intValue(); // 拆箱

之后

Integer i = 1; // 装箱
int j = i; // 拆箱

 

2.自动装箱和拆箱多了什么步骤?

public class Test {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Integer i = 1;
		int j = i;
	}

}

我们先编译这段代码获得.class文件,然后使用javap -c反编码文件获得字节码指令。

invokestatic的意思是调用了静态方法, 后面的注释也告诉了我们调用了什么方法。 invokevirtual是调用实例方法。

得出的结论是自动装箱会调用valueOf方法,自动拆箱则会调用xxxValue方法(xxx是基本类型)。

 

3.常见的问题

你们知道下面这段代码的结果吗?

public class Test {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Integer a1 = 1;
		Integer a2 = 2;
		Integer a3 = 126;
		Integer a4 = 126;
		Integer a5 = 127;
		Integer a6 = 128;
		Integer a7 = 128;
		
		Integer b1 = 1024;
		Integer b2 = 1024;
		
		Long c1 = 3L;
		Long c2 = 0L;
		
		Double d1 = 1D;
		Double d2 = 1D;
		
		System.out.println(a1 + a3 == a5);
		System.out.println(a2 + a3 == a6);
		System.out.println(a3 == a4);
		System.out.println(a6 == a7);
		System.out.println(a6 - a5 == a1);
		System.out.println(a6.equals(a1 + a5));
		System.out.println(a5.equals(a1 + a3));
		
		System.out.println(b1 == b2);
		System.out.println(b1 - a1 == b2 - a1);
		
		System.out.println(c1 == a1 + a2);
		System.out.println(c1.equals(a1 + a2));
		System.out.println(c1.equals(a1 + a2 + c2));
		
		System.out.println(d1 == d2);
		
		
	}

}

还是让我们先看看valueOf、xxxValue的源码吧。已Integer为例。

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
}

这是Integer类valuOf的一段源码,当装箱时,如果值在[IntegerCache.low, 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 =
                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() {}
    }

查看IntegerCache的源码可知,low的值为-128,而high的值可以指定,默认是127。也就是说在正常情况下[-128, 127]内的int在装箱的时候不会产生新对象。

那Long、Double呢?

public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
}

public static Double valueOf(double d) {
        return new Double(d);
}

我们发现Long也是有缓存的,缓存是-128~127,无值指定最高范围。Double是没有缓存的,就说明只要装箱就会生成一个新对象。

最后我们看一下Integer的equals,结论就是传入的对象必须是同类型的才会进行数值比较,否则直接false。

eq: Integer 1 不等于 Long 1

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

现在就可以解决上面的问题了

public class Test {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Integer a1 = 1;
		Integer a2 = 2;
		Integer a3 = 126;
		Integer a4 = 126;
		Integer a5 = 127;
		Integer a6 = 128;
		Integer a7 = 128;
		
		Integer b1 = 1024;
		Integer b2 = 1024;
		
		Long c1 = 3L;
		Long c2 = 0L;
		
		Double d1 = 1D;
		Double d2 = 1D;
		
		// 因为对象无法进行加法运行, 会自动拆箱, 所以这里进行的是数值比较
		System.out.println(a1 + a3 == a5); // true
		System.out.println(a2 + a3 == a6); // true
		// 在缓存范围内, 是同一个对象
		System.out.println(a3 == a4); // true
		// 不在缓存范围内, 不是同一个对象
		System.out.println(a6 == a7); // false
		System.out.println(a6 - a5 == a1); // true
		// 这里进行了3步
		// 1. 对象拆箱进行加法运行
		// 2. 对结果进行装箱
		// 3. 对参数进行拆箱进行数值比较
		System.out.println(a6.equals(a1 + a5)); // true
		System.out.println(a5.equals(a1 + a3)); // true
		
		
		System.out.println(b1 == b2); // false
		System.out.println(b1 - a1 == b2 - a1); // true
		
		System.out.println(c1 == a1 + a2); // true
		// 因为a1 + a2的结果是int, 自动装箱为Integer, 无法转化为Long, 直接返回false
		System.out.println(c1.equals(a1 + a2)); // false
		// a1 + a2 + c2结果为long,可以进行数值比较
		System.out.println(c1.equals(a1 + a2 + c2)); // true
		
		// Double没有缓存
		System.out.println(d1 == d2); // false
	}

}

 

4.性能问题

频繁的装箱性能是非常的低下, 因为要频繁的创建、回收对象。特别是Double,Double没有缓存,每装箱一次就会产生一个新的对象。

public class Test {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		int i = 0;
		Integer j = 0;
		long startTime, endTime;
		
		startTime = System.currentTimeMillis();    //获取开始时间
		while (i < 999999999) {
			++i;
		}
		endTime = System.currentTimeMillis();    //获取结束时间
		System.out.println("基本数据类型:" + (endTime - startTime) + "ms");    //输出程序运行时间
		
		startTime = System.currentTimeMillis();    //获取开始时间
		while (j < 999999999) {
			++j;
		}
		endTime = System.currentTimeMillis();    //获取结束时间
		System.out.println("包装类型:" + (endTime - startTime) + "ms");    //输出程序运行时间
		
	}

}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值