可能你不知道的,关于自动装箱和自动拆箱(代码片段)

sum-41 sum-41     2022-12-06     792

关键词:

包装类

我们知道,Java中包含了8种基本数据类型:

  • 整数类型:byte、short、int、long
  • 字符类型:char
  • 浮点类型:float、double
  • 布尔类型:boolean

这8种基本数据类型的变量不需要使用new来创建,它们不会在堆上创建,而是直接在栈内存中存储,因此会比使用对象更加高效。

但是,在某些时候,基本数据类型会有一些制约,例如当有个方法需要Object类型的参数,但实际需要的值却是2、3等数值,这就比较难以处理了。因为,所有引用类型的变量都继承了Object类,都可当成Object类型变量使用,但基本数据类型的变量就不可以了。

为了解决这个问题,Java为这8种基本数据类型分别定义了相应的引用类型,并称之为基本数据类型的包装类(Wrapper Class)。包装类均位于java.lang包下,其和基本数据类型的对应关系如下表所示:

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean

从上表可以看出,除了int和char有点例外之外,其他的基本数据类型对应的包装类都是将其首字母大写。

自动装箱和自动拆箱

在Java SE5之前,把基本数据类型变量变成包装类实例需要通过对应包装类的构造器来实现,即:

Integer i = new Integer(10);

把包装器类型转换为基本数据类型需要这样:

int a = i.intValue();

上面的基本数据类型与包装类对象之间的转换有点繁琐,所以从Java SE5开始,为了简化开发,Java提供了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)功能。

所谓自动装箱,就是自动将基本数据类型转换为包装器类型;自动拆箱,就是自动将包装器类型转换为基本数据类型,下面代码演示自动装箱、拆箱。

// 自动装箱
Integer i = 10;
// 自动拆箱
int a = i;

我们可以看到,当JDK提供了自动装箱和自动拆箱功能后,大大简化了我们的开发。

需要注意的是,进行自动装箱和自动拆箱时必须注意类型匹配。例如,Integer只能自动拆箱成int,int只能自动装箱成Integer。

原理

通过前文,我们已经知道了什么是包装类及什么是自动装、拆箱。

那么接下来,我们来看一下自动拆、装箱的原理。有如下代码:

public static void main(String[] args) 
    Integer i = 10;// 自动装箱
    int a = i;// 自动拆箱

对以上代码进行反编译,得到如下代码:

public static void main(String[] args) 
    Integer i = Integer.valueOf(10); 
    int a = integer.intValue(); 

从反编译后得到的代码可以看出,在装箱的时候自动调用的是Integer的valueOf(int i)方法,而在拆箱的时候自动调用的是Integer的intValue()方法。

其他的也类似,比如Double、Character。感兴趣的同学,可以自己尝试一下。

因此可以用一句话总结装箱和拆箱的实现过程:

自动装箱,都是通过包装类的valueOf()方法来实现的。

自动拆箱,都是通过包装类对象的xxxValue()来实现的。

使用场景

我们了解过原理之后,在来看一下,什么情况下,Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了,那是最简单的也最容易理解的。

我们主要来看一下,那些可能被忽略的场景

场景1、将基本数据类型放入集合类
List<Integer> list = new ArrayList<>();
list.add(1);

当我们把基本数据类型放入集合类中的时候,代码没有报错,很明显,这里发生了自动装箱。

将上面代码进行反编译,也印证了这一点:

List<Integer> list = new ArrayList<>();
list.add(Integer.valueOf(1));
场景2、包装类型和基本类型的大小比较
Integer i = 10;
// 输出true
System.out.println("10的包装类实例是否大于8?" + (i > 8));

反编译上面代码:

Integer i = Integer.valueOf(10);
System.out.println("10的包装类实例是否大于8?" + (i.intValue() > 8));

可以看到,当包装类与基本数据类型进行比较运算时,是先将包装类进行拆箱成基本数据类型,然后进行比较的。

场景3、包装类型的运算
Integer i = 10;
Integer j = 20;
// 输出30
System.out.println(i + j);

反编译上面代码:

Integer i = Integer.valueOf(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());

可以看到,两个包装类型之间的运算,会被自动拆箱成基本类型进行。

场景4、三目运算符的使用
boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;

很多人不知道,其实在第四行,会发生自动拆箱,反编译后代码如下:

boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;

这其实是三目运算符的语法规范:当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。如果这个时候i的值为null,那么就会发生NPE,这一点,是我们日常开发过程中的大坑。

观察以下代码:

public static void main(String[] args) 
    Map<String, Boolean> map = new HashMap<>();
    Boolean b = map != null ? map.get("test") : false;
    System.out.println(b);

一般情况下,我们会认为以上代码Boolean b的最终得到的值应该是null。因为map.get("test")的值是null,而b又是一个对象,所以得到结果会是null

但是,以上代码会抛出NPE:

Exception in thread "main" java.lang.NullPointerException

这是因为,map.get("test") == null,当用null去调用intValue()方法时,抛出了NPE。

Integer的缓存机制

有如下代码,你知道输出结果是什么吗?

public static void main(String[] args) 
    Integer a = 1;
    Integer b = 1;
    Integer c = 128;
    Integer d = 128;
    System.out.println(a == b);
    System.out.println(c == d);

我们都知道在Java里,当用==来比较两个对象时,比较的是地址,如果两个对象引用指向堆中的同一块内存就返回true,否则返回false。这一点是完全正确的。

那按照这个理论,上面代码应该输出都是false,因为4个变量都是Integer类型的对象,但实际输出结果却是这样的:

true
false

这让人疑惑:同样是两个int类型的数值自动装箱成Integer对象,如果是两个2自动装箱后就相等;但如果是两个128自动装箱后就不相等,这是为什么呢?

一起来找下这个问题的答案:

  1. Integer a = 1,根据前文我们知道,这里发生了自动装箱,而自动装箱其实就是调用了IntegervalueOf方法
  2. 既然调用了这个valueOf方法,那我们是不是应该去看看这个方法里到底做了什么事情?
  3. 最后去查看Integer这个包装类的valueOf的具体实现。

来,一起来看JDK里valueOf方法的源代码:

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

方法实现很简单,先解释一下,IntegerCacheInteger类中定义的一个private static的内部类,它维护了一个Integer数组cacheIntegerCache源码如下:

    /**
     * Cache to support the object identity semantics of autoboxing for values 
     * between -128 and 127 (inclusive) as required by JLS. 
     * <p>
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the @code -XX:AutoBoxCacheMax=<size> option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
    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 Integer.IntegerCache.high >= 127;
        

        private IntegerCache() 
        
    

从上面源码可以看出:

  • IntegerCache类有一个int类型的常量low,值为-128
  • 还有一个int类型的常量high,它的值通过静态初始化块进行赋值,默认为127(从javadoc可以看到,这个值可以通过虚拟机参数:-XX:AutoBoxCacheMax=进行设置)
  • 最后,还有一个Integer类型的数组cache[],它同样通过静态初始化块进行初始化,长度==(high - low) + 1,默认为(127+128+1)=256,数组元素值为-128~127。
  • ps:大佬们写的代码真的是太优雅了!!

我们终于找到上面问题的答案了:

Integer类初始化时,会把一个-128~127之间的Integer类型对象放入一个名为cache的数组中缓存起来。如果以后把一个-128~127之间的基本数据类型自动装箱成一个Integer实例时(即调用valueOf方法),实际上是直接引用了cache数组中的对应元素。但每次把一个不在-128~127范围内的整数自动装箱成Integer实例时,就需要重新new一个Integet实例,所以出现了上面那样的运行结果。

缓存是一种非常优秀的设计模式,在Java、JavaEE平台的很多地方都会通过缓存来提高系统的性能。

类型的,Byte、Short、Long、Character也有相同的缓存机制,值得注意的是Double、Float是没有缓存机制的。有兴趣的同学,可以自行查看源码。

总结

  1. 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。
  2. 有些场景会进行自动拆装箱,此时要注意包装类对象是否为null,否则自动拆箱时就有可能抛出NPE。
  3. 包装对象的数值比较,不能简单的使用==,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals比较。
    技术图片

java自动装箱和拆箱(待整理)(代码片段)

含义装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。Integeri=10;//装箱intn=i;//拆箱publicclassMainpublicstaticvoidmain(String[]args)Integeri=10;intn=i;从反编译得到的字节码内容... 查看详情

11自动拆箱和装箱(代码片段)

什么是自动拆箱和自动装箱?//自动装箱:把基本类型转换为包装类类型Integers1=123;//自动拆箱:把包装类类型转换为基本类型Integers2=newInteger(10);inti2=s2;以上特性是jdk5中加入的,也就是说在jdk5版本之前是不支持自动装箱和自动拆... 查看详情

装箱与拆箱(代码片段)

装箱与拆箱什么是装箱与拆箱描述语言描述,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。代码描述就是:Integerinteger=100;//自动装箱inti=integer;//自动拆箱基本技术类型对应的... 查看详情

自动装箱和自动拆箱

http://www.cnblogs.com/xrq730/p/4869065.html自动拆箱和自动装箱Java为每种基本数据类型都提供了对应的包装器类型。举个例子:publicclassTestMain{publicstaticvoidmain(String[]args){Integeri=10;}}这个过程中会自动根据数值创建对应的Integer对象,这就... 查看详情

java自动装箱和拆箱

https://www.cnblogs.com/wang-yaz/p/8516151.html 一、什么是自动装箱拆箱 很简单,下面两句代码就可以看到装箱和拆箱过程1//自动装箱2Integertotal=99;34//自动拆箱5inttotalprim=total;简单一点说,装箱就是自动将基本数据类型转换为包装... 查看详情

自动装箱拆箱(代码片段)

...类型的一种却可以计算,原因在于,Java”偷偷地”自动地进行了对象向基本数据类型的转换。相对应的,引用数据类型变量的值必须是new出来的内存空间地址值,而我们可以将一个基本类型的值赋值给一个基本类型包装类... 查看详情

详解java中的自动装箱与拆箱,5000+字,看了不懂你打我(代码片段)

什么是自动装箱拆箱?​很简单,下面两句代码就可以看到装箱和拆箱过程//自动装箱Integertotal=99;//自动拆箱inttotalprim=total;简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包... 查看详情

如何理解java中的自动拆箱和自动装箱?

...面的第一家公司就被面试官给问住了...如何理解Java中的自动拆箱和自动装箱?自动拆箱?自动装箱?什么鬼,听都没听过啊,这...这..知识盲区...回到家后小伟赶紧查资料,我透,这不就是问基本类型跟封装类型吗,面试官整啥... 查看详情

装箱与拆箱(代码片段)

...应的引用类型包装起来拆箱:将包装类型转换为基本类型自动拆装箱:Integer i =10;  //自动装箱  反编译后代码:integeri=Integer.valueOf(10);int b= i;     //自动拆箱 反编译后代码:intb=... 查看详情

装箱与拆箱(代码片段)

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。packagecom.lv.pm;publicclassTest4publicstaticvoidmain(String[]args)//基本数据类型是不是Object的子类?//装箱拆箱//给8种基本数据类型,做一个... 查看详情

自动装箱和拆箱

1.自动装箱  ArrayList<Integer>list=newArrayLlist<Integer>();  list.add(2);自动变换为  list.addInteget.valueOf(2);这种变换称为自动装箱。 2.自动拆箱  int n=list.get(i);以上语句被翻译为  intn=list.get(i).intValue();也就是说... 查看详情

java1.5自动拆箱和装箱的注意事项

背景java1.5后引入了自动装箱和自动拆箱的概念自动拆箱:将引用类型转化为基本数据类型自动装箱:将基本数据类型装为引用类型但是实际使用中,什么情况自动拆箱什么情况自动装箱呢?自动装箱Integeri=100;(调用Integer.valueOf(... 查看详情

java啥是拆箱和装箱,拆箱和装箱嘛用啊???

详解Java的自动装箱与拆箱(Autoboxingandunboxing)一、什么是自动装箱拆箱 很简单,下面两句代码就可以看到装箱和拆箱过程//自动装箱Integer total = 99;//自定拆箱int totalprim = total;简单一点说,装箱就是自动将... 查看详情

自动装箱拆箱

...(num3==i2);//trueSystem.out.prinlt(num3==num4);//false从输出结果可以知道,在装箱、拆箱中基本数据类型和Integer类型的数值比较总是相等,因为==对于基本类型就是比较值但是,当同样是I 查看详情

java基础自动装箱和拆箱面试题

JDK1.5(以后的版本)的新特性自动装箱和拆箱1.自动装箱:把基本类型转换为包装类类型 inta=10; Integeri=newInteger(a); Integervalue=10; 为什么基本类型就能直接转化为Integer,Integer不应该是new出来的吗 内部会自动的newInteger(10)自动装箱 2.... 查看详情

自动装箱和拆箱的原理

http://blog.csdn.net/jairuschan/article/details/7513045http://www.importnew.com/15712.html 查看详情

自动装箱拆箱(autoboxing,unboxing)

自动装箱和拆箱https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html1.5才有(Autoboxing,Unboxing)自动装箱(Autoboxing)是java编译器在java基本类型和对应的对象包装类型上做的自动转换自动装箱如int转成Integer,double转成Double拆箱(Unboxing)Double... 查看详情

为啥我们在 Java 中使用自动装箱和拆箱?

】为啥我们在Java中使用自动装箱和拆箱?【英文标题】:WhydoweuseautoboxingandunboxinginJava?为什么我们在Java中使用自动装箱和拆箱?【发布时间】:2015-02-2304:36:24【问题描述】:自动装箱是Java编译器进行的自动转换在原始类型及其... 查看详情