如何通过源码了解Java的自动装箱拆箱详解

 更新时间:2022年04月20日 15:55:57   作者:一个程序员的成长  
装箱就是把基本类型转换成包装类,拆箱就是把包装类转换成基本类型,下面这篇文章主要给大家介绍了关于如何通过源码了解Java的自动装箱拆箱的相关资料,需要的朋友可以参考下

什么叫装箱 & 拆箱?

将int基本类型转换为Integer包装类型的过程叫做装箱,反之叫拆箱。

首先看一段代码

public static void main(String[] args) {
    Integer a = 127, b = 127;
    Integer c = 128, d= 128;
    System.out.println(a == b); // true
    System.out.println(c == d); // false
}

不知道还有没有人不知道这段代码出现true和false的原因。由此我们引出了Java装箱的这个操作。我们带着疑问去进行分析。

装箱(valueOf())

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

我们可以发现,在最开始有一个判断,如果这个值的范围在[-128,127]之间,那么就从这个缓存(Integer数组)中取,如果不在这个范围那么直接new一个。

为什么要有[-128,127]的缓存?

我说说的理解,因为在我们的业务中,可能存在各种状态和标识等Integer类型的字段,这些值一般都是0,1,2,3之类的,而且出现的比较频繁,如果没有缓存,那么就需要频繁的new对象,然后再释放,就非常消耗内存空间,所以对于这个缓存就出现了,可以极大的帮助我们优化一些空间上的浪费。

为什么是[-128,127]?

这个我看了一下,具体为什么这里就不详说了,主要还是依赖计算机基础知识,在你了解了什么是原码、反码、补码。就很容易知道为什么是这个范围区间了。

这个值也是可以通过启动参数进行更改的。

-XX:AutoBoxCacheMax=(size)

自动装箱带来的性能问题

那么看到现在你应该明白上面代码出现不同结果的原因了,那么你有没有想过,比如我们业务中一个for循环中,出现了统计数据类似这样的操作,如果存在自动装箱,那么会出现什么问题?我们看下面一段代码。

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    Integer count = 0;
    // int count = 0;
    for (int i = 0; i < 5000000; i++) {
        count += i;
    }
    System.out.println("计算时长:" + (System.currentTimeMillis() - startTime) + " ms");
}

// 执行结果:
// Integer 计算时长:51 ms
// int 计算时长:6 ms

那么通过执行结果可以明显的发现自动装箱频繁的new对象、分配内存,造成时间和空间上的性能损耗。

小总结

通过上面的源码阅读和测试分析,我们可以得出结论,我们平时在进行计算统计,或者方法入参的时候,应该尽量的避免这种类型转换的问题。来提升我们整个代码的执行效率。

拆箱(intValue)

拆箱总体没有什么复杂的逻辑,直接返回这个数值的基本类型。

补充:自动装箱、拆箱总是会发生吗?

其实不一定。看下面的一段示例代码,输出结果已被注释在输出语句后面。

public static void main(String[] args) {
// TODO 自动生成的方法存根
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c==d);//true
//包装类的==在没有遇到算术运算的情况下不会自动拆箱
System.out.println(e==f);//false
System.out.println(c==(a+b));//true
System.out.println(c.equals(a+b));//true
System.out.println(g==(a+b));//true
//equals方法不会处理数据转型关系
System.out.println(g.equals(a+b));//false
}

发生自动装箱、拆箱的情况如下:

自动装箱:基本类型赋值给包装类型。如:Integer i1 = 1;

自动拆箱:

  1. 包装类型赋值给基本类型。如:int i2 = new Integer(1);
  2. int类型与Integer类型比较。int类型与Integer类型比较如果值相等则结果总是为true。
  3. Integer类型遇到算术运算

但是为什么在上例中,System.out.println(c==d);与System.out.println(e==f);输出的结果不一样呢?

主要是因为Integer.valueOf()方法。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() {}
    }
    
  public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

IntegerCache 是Integer的静态内部类,valueOf()是包装方法。从源码中可以看出,cache是一个缓存数组,当valueOf()方法的入参i在[-128,127]区间内,就会返回缓存数组中的Integer值,否则会重新new一个Integer。

这就是System.out.println(c==d);与System.out.println(e==f);输出结果不同的原因。c和d在缓存区间内,所以返回的是同一个引用;而e和f不在缓存区间内,返回的都是new Integer,已经不是同一个引用。

总结

到此这篇关于如何通过源码了解Java的自动装箱拆箱的文章就介绍到这了,更多相关Java自动装箱拆箱内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决IDEA2020 创建maven项目没有src/main/java目录和webapp目录问题

    解决IDEA2020 创建maven项目没有src/main/java目录和webapp目录问题

    这篇文章主要介绍了IDEA2020 创建maven项目没有src/main/java目录和webapp目录问题解决方法,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • SpringBoot如何基于POI-tl和word模板导出庞大的Word文件

    SpringBoot如何基于POI-tl和word模板导出庞大的Word文件

    这篇文章主要介绍了SpringBoot如何基于POI-tl和word模板导出庞大的Word文件,poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库
    2022-08-08
  • Java从源码看异步任务计算FutureTask

    Java从源码看异步任务计算FutureTask

    这篇文章主要介绍了Java从源码看异步任务计算FutureTask,FutureTask就能够很好的帮助我们实现异步计算,并且可以实现同步获取异步任务的计算结果,具体是怎样实现的,下面我们就一起来学习下面文章的具体内容吧
    2022-04-04
  • 使用Spring源码报错java:找不到类 InstrumentationSavingAgent的问题

    使用Spring源码报错java:找不到类 InstrumentationSavingAgent的问题

    这篇文章主要介绍了使用Spring源码报错java:找不到类 InstrumentationSavingAgent的问题,本文给大家分享解决方法,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 在JDK和Eclipse下如何编写和运行Java Applet

    在JDK和Eclipse下如何编写和运行Java Applet

    本文主要介绍了在JDK和Eclipse的环境下如何编写和运行Java Applet,图文方式,适合初学者学习。
    2015-09-09
  • Mybatis中Mapper标签总结大全

    Mybatis中Mapper标签总结大全

    这篇文章主要介绍了Mybatis中Mapper标签总结大全,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 如何利用rabbitMq的死信队列实现延时消息

    如何利用rabbitMq的死信队列实现延时消息

    这篇文章主要介绍了如何利用rabbitMq的死信队列实现延时消息问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • 解决spring @ControllerAdvice处理异常无法正确匹配自定义异常

    解决spring @ControllerAdvice处理异常无法正确匹配自定义异常

    这篇文章主要介绍了解决spring @ControllerAdvice处理异常无法正确匹配自定义异常的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 详解Maven POM(项目对象模型)

    详解Maven POM(项目对象模型)

    这篇文章主要介绍了Maven POM(项目对象模型)的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • java基础之方法详解

    java基础之方法详解

    这篇文章主要介绍了java基础之方法详解,文中有非常详细的代码示例,对正在学习java基础的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04

最新评论