【Java5 特性】 1.自动拆装箱

一、自动拆装箱的原理

Java中有8大基本数据类型,为了实现面向对象的操作,8大基本类型又都有着各自对应的包装对象类型,而包装类型大体上可以分为三种:Boolean、Character和Number,其中Number类型包括了整型(Byte、Short、Integer、Long)、浮点型(Float、Double)。用于基本数据类型与其对应的包装数据类型之间的转换,这种方式称为自动拆装箱,它是Java5 引入的一种特性,也可以理解为是一种Java语法糖(语法糖概念:借助编译器实现了基于编译器的新语法,增强了Java程序的可读性,便于开发人员使用和维护)。

下面通过示例演示一下自动拆装箱的原理, 以Integer类型为例: 

/** TestInt.java源码 */
public class TestInt {
  public static void main(String[] args) {
      Integer a= 50; // 50为基本类型
      int i = a;  // a为包装类型
  }
}

经过JVM编译之后生成了class文件,这里很容易看出自动拆装箱的实现过程:

/** TestInt.class */
public class TestInt {
  public static void main(String[] paramArrayOfString) {
    Integer integer = Integer.valueOf(50);
    int i = integer.intValue();
  }
}

小结一下:

  • 自动装箱:通过valueOf()方法实现,将 基本数据类型 转换成 对应的包装数据类型;
  • 自动拆箱:通过xxxValue()方法实现,将 包装数据类型 转换成 对应的基本数据类型;

二、自动拆装箱的应用场景

1、Java集合泛型

注意:集合的参数化类型只能是包装类型,不能是对应的基本类型!!!

List<Integer> list = new ArrayList<>();
for (int i = 1; i < 50; i ++){
    list.add(i); // 自动装箱
}

Map<Long, Double> map = new HashMap<>();
map.put(1001L, 50.6); // 自动装箱
map.put(1002L, 63.9);

2、进行运算和比较

注意:

  • 实现+号的数值运算的两边必须是基本类型,基本类型的==比较是一种数值比较;
  • 包装类中均重写了equals()方法,因此equals()则比较的是对象内容,但equals()比较时不参与类型转换;
Integer a = 10; 
Integer b = 10; 
Integer c = 20; 
System.out.println(c == (a + b)); // true
System.out.println(c.equals(a + b)); // true, 整型20与整型20比较

Long d = 20L;
System.out.println(d == (a + b)); // true
System.out.println(d.equals(a + b)); // false, 长整型20L与整型20比较

3、方法的入参与返回值

/** 自动拆箱 */
public int getNum(Integer num) {
    return num;
}
/** 自动装箱 */
public Integer findNumber(int number) {
    return number;
}

三、自动拆装箱与缓存

还是先看个测试示例,创建a,b,c,d四个对象,然后分别用== 和 equals()比较:

public static void main(String[] args) {
    Integer a = 200;
    Integer b = 200;
    Integer c = 120;
    Integer d = 120;
    System.out.println(a == b);  
    System.out.println(a.equals(b)); 
    System.out.println(c == d); 
    System.out.println(c.equals(d)); 
}
--------------------------
打印结果:
    false
    true
    true
    true

因为包装类Integer重写了equals()方法,对比的结果均为true也很好理解。而 == 比较的则是对象的引用(即内存地址),在Integer类型中,a==b为false,c==d却为true,这说明了c与d是同一个对象!!将上面的换成Short 类型、Long 类型、Byte 类型,得到的仍是相同结果;换成Float类型、Double类型的话,a==b为false,c==d为false,说明了c与d不是同一个对象。

从上述的测试结果来看:Byte 类、Short 类、Integer 类、Long 类应该存在一种类似缓存的功能,打开源码后,发现它们确实都存在一个名叫XxxCache的静态内部类,该缓存内部类中均维护了一个缓存数组,将-128到127区间的数组缓存了起来。当创建包装对象时,如果包装对象的数值在该区间范围的话,则不会再去new新对象了,这样可以减少不必要的创建对象的开销操作。另外,因为Float和Double类型均带有浮点小数,没有特定的经常用到的热点值,没必要使用缓存。

当然,Character类型也是有缓存功能的,只不过是将0到128之间的char类型数据做了缓存,Boolean类则只存在TRUE 和 FALSE两个值,也没必要使用缓存。

值得注意的是,源码中Byte 类、Short 类、Long 类实现缓存的步骤和细节是一样,Integer 类则略显不同,可概括为如下:

/**  Byte类、Short类、Long类实现缓存功能 */
private static class XxxCache {
    private XxxCache(){}
    // 定义缓存数组大小
    static final Xxx cache[] = new Xxx[-(-128) + 127 + 1];
    // 静态块只在类首次加载时进行一次初始化,将-128到127存到缓存数组中
    static {
        for(int i = 0; i < cache.length; i++)
            // 默认为int,若使用byte和short需要强转
            cache[i] = new Xxx((xxx)(i - 128));
    }
}

/**  Integer类实现缓存功能 */
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 类实现缓存功能似乎有点特殊了,但核心思路还是类似的。仔细看会发现有个很明显的区别 —— Integer的最大缓存值可配置,在运行时可指定缓存最大值,如 -XX:AutoBoxCacheMax=10000。

小结

自动拆装箱的引入给开发带来了使用便利,尤其是Character、Byte 、Short 、Integer 、Long等类型为常用数值区间做了缓存,避免了不必要的对象创建开销。实际使用中还是需要注意一些细节问题的,比如包装类的==与equals()的比较、包装对象为null要避免NPE情况、避免在循环里进行大量频繁的拆装箱操作等等。自动拆装箱是基础,也是需要必知必懂的,不积硅步无以至千里,点滴付出终将有所收获,共同进步吧 ~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值