Java自动拆箱与装箱

微信公众号:如有问题或建议,请公众号留言

最近更新:2018-08-19

包装类型

在讲解正文之前,我很想问这么一个问题:"Java为我们提供了8种基本数据类型,为什么还需要提供各自的包装类型呢?"。您可能会觉得这个问题问的很奇怪,但是我觉得还是值的思考的。

因为Java是一门面向对象的语言,基本数据类型并不具备对象的性质。而包装类型则是在基本类型的基础上,添加了属性和方法,从而成为了对象。试想,一个int类型怎么添加到List集合中?

自动拆箱与装箱

概念:

首先,我们来看一个例子:

1Integer number = 10; //自动装箱
2int count = number; //自动拆箱
复制代码

基本数据类型自动转化为包装类型时,即为自动装箱。

反之,包装类型自动转化为基本数据类型时,即为自动拆箱。

看概念您是不是觉得很简单,但真的这么"简单"吗?不妨我们来看两道题。

实操:

  • 题目一:
 1int z = 127;
 2Integer a = 127;
 3Integer b = Integer.valueOf(127);
 4Integer f = new Integer(127);
 5Integer g = new Integer(127);
 6
 7int y = 128;
 8Integer c = 128;
 9Integer d = Integer.valueOf(128);
10Integer e = new Integer(128);
11Integer h = new Integer(128);
12
13System.out.println("z==a结果:" + (z == a));
14System.out.println("z==b结果:" + (z == b));
15System.out.println("z==f结果:" + (z == f));
16System.out.println("a==b结果:" + (a == b));
17System.out.println("a==f结果:" + (a == f));
18System.out.println("f==g结果:" + (f == g));
19
20System.out.println("y==c结果:" + (y == c));
21System.out.println("y==d结果:" + (y == d));
22System.out.println("y==e结果:" + (y == e));
23System.out.println("c==d结果:" + (c == d));
24System.out.println("c==e结果:" + (c == e));
25System.out.println("e==h结果:" + (e == h));
复制代码
  • 题目二:
 1Integer i = 1;
 2Integer j = 2;
 3Integer k = 3;
 4Long m = 3L;
 5Long n = 2L;
 6System.out.println("k==i+j结果:" + (k == i + j));
 7System.out.println("k.equals(i+j)结果:" + (k.equals(i + j)));
 8System.out.println("m==i+j结果:" + (m == i + j));
 9System.out.println("m.equals(i+j)结果:" + (m.equals(i + j)));
10System.out.println("m.equals(i+n)结果:" + (m.equals(i + n)));
复制代码

思考:

  • 什么情况会触发自动装箱操作?
         当基本类型赋值给包装类型引用时,会触发自动装箱操作,调用包装类型的valueOf()方法。
  • 什么情况会触发自动拆箱操作?
         当包装类型参与运算时会触发自动拆箱操作,调用包装类型对应的***Value()方法,例如Integer类为intValue()。Why?因为运算是基本数据类型要做的事情。

补充: 此处利用java提供的反汇编器javap,查看java编译器为我们生成的字节码。通过它,我们可以对照源代码和字节码,来对上述观点进行论证。

1Integer number = 10;//自动装箱
2int number2 = 10;
3Integer number3 = 10;
4System.out.println(number == number2);//自动拆箱
5System.out.println(number + number3);//自动拆箱
6System.out.println(number.equals(number2));//自动装箱
复制代码
Java自动拆箱与装箱

图注:infoQc公众号

由图中可以看出,执行过程确实如上述代码末尾注释,并且自动装箱调用了valueOf()方法,自动拆箱调用了intValue()方法。

  • Integer的valueOf()和intValue()又是如何实现的呢?

话不多说,我们来看下Integer这两个方法的源码

1public int intValue() {
2    return value;
3}
4
5public static Integer valueOf(int i) {
6    if (i >= IntegerCache.low && i <= IntegerCache.high)
7        return IntegerCache.cache[i + (-IntegerCache.low)];
8    return new Integer(i);
9}
复制代码

intValue()很简单,返回Integer对象的value值。

valueOf()判断当前int值是否在IntegerCache的区间内(-128~127),在则从IntegerCache的cache数组中获取指定位置的Integer对象,否则创建一个Integer对象,value值为i。

抱着打破砂锅问到底的态度,我们继续看下IntegerCache源码:

 1private static class IntegerCache {
 2    static final int low = -128;
 3    static final int high;
 4    static final Integer cache[];
 5
 6    static {
 7        // high value may be configured by property
 8        int h = 127;
 9        String integerCacheHighPropValue =
10            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11        if (integerCacheHighPropValue != null) {
12            try {
13                int i = parseInt(integerCacheHighPropValue);
14                i = Math.max(i, 127);
15                // Maximum array size is Integer.MAX_VALUE
16                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17            } catch( NumberFormatException nfe) {
18                // If the property cannot be parsed into an int, ignore it.
19            }
20        }
21        high = h;
22
23        cache = new Integer[(high - low) + 1];
24        int j = low;
25        for(int k = 0; k < cache.length; k++)
26            cache[k] = new Integer(j++);
27
28        // range [-128, 127] must be interned (JLS7 5.1.7)
29        assert IntegerCache.high >= 127;
30    }
31
32    private IntegerCache() {}
33}
复制代码

IntegerCache作为Integer私有静态内部类,定义了静态语句块,在类加载时会被调用。做什么呢?按照low和high默认为-128~127,依次创建对应数值的Integer对象,并放入cache数组中,作为缓存。通过valueOf()源码,我们知道,那里就是体现它缓存价值的地方。

补充:

缓存里下限-128不可变,上限默认为127,可以通过java -Djava.lang.Integer.IntegerCache.high=***来动态调整。

实操分析:

有了上述的知识储备,我们回顾一下之前的两道题目,是不是容易了很多?

 1int z = 127;
 2Integer a = 127;
 3Integer b = Integer.valueOf(127);
 4Integer f = new Integer(127);
 5Integer g = new Integer(127);
 6
 7int y = 128;
 8Integer c = 128;
 9Integer d = Integer.valueOf(128);
10Integer e = new Integer(128);
11Integer h = new Integer(128);
12
13System.out.println("z==a结果:" + (z == a));//true 和基本数据类型做==,比较的是值,故包装类型会自动拆箱
14System.out.println("z==b结果:" + (z == b));//true 同理,自动拆箱
15System.out.println("z==f结果:" + (z == f));//true 同理,自动拆箱
16System.out.println("a==b结果:" + (a == b));//true 因127在IntegerCache的-128~127范围内,故采用缓存里的对象,故二者引用地址一致
17System.out.println("a==f结果:" + (a == f));//false 对象之间==,比较的是引用,因为a指向IntegerCache里缓存的对象,f指向的new出来的对象,故引用地址不同
18System.out.println("f==g结果:" + (f == g));//false f和g都是自己new出来的对象,故引用地址不同
19
20System.out.println("y==c结果:" + (y == c));//true 自动拆箱
21System.out.println("y==d结果:" + (y == d));//true 自动拆箱
22System.out.println("y==e结果:" + (y == e));//true 自动拆箱
23System.out.println("c==d结果:" + (c == d));//false 因128不在IntegerCache的-128~127范围内,故采用缓存里的对象,故二者引用地址一致
24System.out.println("c==e结果:" + (c == e));//false 引用地址不同
25System.out.println("e==h结果:" + (e == h));//false 引用地址不同
复制代码
  • 题目二解析:
 1Integer i = 1;
 2Integer j = 2;
 3Integer k = 3;
 4Long m = 3L;
 5Long n = 2L;
 6
 7System.out.println("k==i+j结果:" + (k == i + j));//true 首先+操作是数值操作,故i和j会自动拆箱,结果为基本类型int 3。然后k自动拆箱,进行值比较3==3
 8System.out.println("k.equals(i+j)结果:" + (k.equals(i + j)));//true 首先+操作是数值操作,故i和j会自动拆箱,结果为基本类型int 3。
 9// 然后调用equals(Object o)方法,此时int 3自动装箱,为Integer 3。Integer类的equals方法里比较的是值,故为true
10
11System.out.println("m==i+j结果:" + (m == i + j));//true 先i和j自动拆箱,再m自动拆箱,比较值。int自动提升为long,值一致为true
12System.out.println("m.equals(i+j)结果:" + (m.equals(i + j)));//false 先i和j自动拆箱,equals(Object o)再自动装箱,为Integer 3。Long和Integer类型不一致,返回false
13System.out.println("m.equals(i+n)结果:" + (m.equals(i + n)));//true 先i和j自动拆箱,int自动提升为long,故为long 3L。再自动装箱为Long 3,返回true
复制代码

Java缓存机制

Java为了在自动装箱时避免每次去创建包装类型,采用了缓存技术。即在类加载时,初始化一定数量的常用数据对象(即常说的热点数据),放入缓存中,等到使用时直接命中缓存,减少资源浪费。

通过查看源码发现,Java提供的8种基本数据类型,除了float和double外,都采用了缓存机制。其实原因也很简单,因为浮点型数据,热点数据并不好确定,故并未采用。

最后给出各类型缓存范围:

Character: 0~127

Byte,Short,Integer,Long: -128 到 127

Boolean: true,false

大家再遇到类似题目时,就可以正确的做出判断啦。

原文 

https://juejin.im/post/5b84c350e51d4538a751f8c0

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java自动拆箱与装箱

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址