Java自动装箱和拆箱


title:Java自动装箱和拆箱

date:2017年10月27日15:04:35

categories: Java基础


前言

相信大家在写代码的时候,都写过至少看到过这样的代码:

Integer i=100;

那你写的时候,有没有想过为什么可以直接这样写呢,Integer可是一个对象呀,可不是个基本类型,为什么可以直接写Integer i=100呢?应该是

Integer i=new Integer(100);

这样才对把。那么到底为什么在Java里面我们允许这样写呢?

这就涉及到我们今天要探讨的Java自动装箱和拆箱了。

当我们学习完Java自动装箱和拆箱,我们就能很好的把上面的问题解决。

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

自动拆装箱分析

  • Example 0

首先,我们还是通过一个例子来分析(JDK1.8)

package com.wangcc.MyJavaSE.box;

import org.junit.Test;

public class IntegerTest {
    @Test
    public void test() {
        int i=125;
        Integer i1=new Integer(125);
        Integer i2=Integer.valueOf(125);
    //  int i2_0=i2.intValue();
        /*Integer i3=125;
         *系统为我们执行了: 
Integer i3 = Integer.valueOf(99); 

         */
        Integer i3=125;
        //基本类型和包装类型使用==比较时,包装类型会自动拆箱
        /*
         * Integer i1=new Integer(125);
         * int i1_0=i1.intValue();
         * i==i1_0 ?
         * */
        System.out.println(i==i1); //(1) true
        System.out.println(i==i2); //(2) true
        System.out.println(i==i3); //(3) true
        System.out.println(i1==i2);
        System.out.println(i1==i3);
        System.out.println(i2==i3);

    }
}

我们可以看到执行后的输出如下:

true
true
true
false
false
true

当基本类型和包装类型使用==比较

我们先看(1)的输出结果为什么是true

当i与i1比较的时候,是基本类型与包装类型去比较,这个时候包装类型会自动进行拆箱操作,这部分工作应该是由编译器优化完成的把。(只是个人猜测 javac的功劳)

相当于会执行如下代码:

         Integer i1=new Integer(125);
         int i1_0=i1.intValue();
         i==i1_0  //最后使用Integer对应的基本类型的值来和基本类型的值直接比较

所以我们会得到true

当Integer i3=125;和Integer i2=Integer.valueOf(125);比较

首先我们可以看到结果是true,那为什么是true呢.

先我们就需要看Integer i3=125;

为什么可以这样写呢?实际上这应该也是编译器对这行代码进行了自己的优化。相当于我们编写了如下代码:

Integer i3 = Integer.valueOf(125); 

也就是说实际上i2和i3都相当于执行了Integer.valueOf(125)得到的

为什么两次执行这个方法都得到得到的是同一个Integer对象呢?我们得看下相关源码

public static Integer valueOf(inti) {    
    if(i >= -128 &&i <=IntegerCache.high)    
       //如果i在-128~high之间,就直接在缓存中取出i的Integer类型对象  
       return IntegerCache.cache[i + 128];    
    else  
       return new Integer(i); //否则就在堆内存中创建   
}    
  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;//h值,可以通过设置jdk的AutoBoxCacheMax参数调整
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                 // 取较大的作为上界,但又不能大于Integer的边界MAX_VALUE  

                    i = Math.max(i, 127);//上界最小为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]; //上界确定,此时high默认一般是127  
        // 创建缓存块,注意缓存数组大小  
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);// -128到high值逐一分配到缓存数组  

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

我们将上述代码重要部分都加上了注释,我们特别注意在确定i的取值时,我们标注了i的最小值是127,也就是说

Integer的缓存至少要覆盖[-128, 127]的范围,那么为什么符合规范的Java实现必须保证Integer的缓存至少要覆盖[-128, 127]的范围

这个有官方说明:(The Java Language Specification, 3rd Edition)

If the value p being boxed is true, false, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.

为了便于理解,我们给出大概的中文意思

为了节省内存,对于下列包装对象的两个实例,当它们的基本值相同时,他们总是==:  
 Boolean  
 Byte  
 Character, \u0000 - \u007f(7f是十六进制的127)  
 Integer, -128127  

好吧,我还是没理解上面那段英文的意思。

这里给出除了Integer之外的其他类型的自动装箱池

Boolean:(全部缓存)
Byte:(全部缓存 -128 — 127)

Character(0-127缓存)
Short(-128127缓存)
Long(-128127缓存)

Float(没有缓存)
Doulbe(没有缓存)

为什么Float和Double没有缓存呢,其实很简单,因为在一定的范围呢,整型的个数是可以确定的,所以可以指定一个缓存数组,但是在一定的范围内,浮点型的个数是无法确定的,所以,没法指定一个缓存数组。

上面一开始有说过,我们可以通过设置VM虚拟机参数来动态的改变Integer缓存区间大小。通过设置

-XX:AutoBoxCacheMax=NNN参数即可将Integer的自动缓存区间设置为[-128,NNN]。这个参数是server模式专有的。

首先你要确认你的Java服务运行所用到的jdk的默认运行模式是多少,使用javac -version参看,我本机安装的JDK8默认就是Server 模式。

如果你默认的是Client模式的话,你就需要作出一些调整了,找到你所安装jdk的地方,进入到jdk/jre/bin目录,然后搜索jvm.cfg文件,修改文件,将Server Client两行顺序做一个前后调整就好。

扯得有点多了,从上面我们可以得知:

当使用

Integer i=Integer.valueOf(x);

当x在-128~127之间的时候,我们得到的Integer对象是缓存IntegerCache中的对象(默认没有修改相关参数的情况下),所以当多次调用这个方法的时候,返回的都是同一对象,而当x的值不在这个范围的时候,多次调用就会多次创建新的Integer对象了。

  • Example 1

    @Test
    public void testUnboxing() {
        Integer num1 = 100;
        Integer num2 = 200;
        Long num3 = 300l;
        System.out.println(num3 == (num1 + num2));
    }

    当你第一眼看到这个代码的时候,你会给出怎样的答案呢?

    true还是false?

    答案是true。如果是单纯的Integer对象和Long类型的对象使用==比较,很显然是无法通过编译的。

    因为不是同一类型,那么如果是使用equals()这种不比较引用的呢?也是false,还是因为是不同类型。

    如果是上述的情况使用==比较。如果在比较的过程中有对某个包装类型进行了+ - * / 等操作时,会自动的进行拆箱操作,最终会比较两端的数值大小。

    当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。

总结

Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率。通过上面的研究和测试,结论如下:

​ (1)Integer和 int之间可以进行各种比较;Integer对象将自动拆箱后与int值比较

​ (2)两个Integer对象之间也可以用>、<等符号比较大小;两个Integer对象都拆箱后,再比较大小

​ (3) 两个Integer对象最好不要用==比较。因为:-128~127范围(一般是这个范围)内是取缓存内对象用,所以相等,该范围外是两个不同对象引用比较,所以不等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值