如何通过源码了解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自动装箱拆箱内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 异常java.lang.NoSuchFieldException解决方案

    Java 异常java.lang.NoSuchFieldException解决方案

    这篇文章主要介绍了Java 异常java.lang.NoSuchFieldException解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • 一文弄懂fastjson

    一文弄懂fastjson

    fastjson 是一个java语言编写的高性能且功能完善的JSON库,本文主要介绍了fastjson的使用,具有一定的参考价值,感兴趣的可以了解一下
    2023-05-05
  • RocketMQ NameServer 核心源码解析

    RocketMQ NameServer 核心源码解析

    这篇文章主要为大家介绍了RocketMQ NameServer 核心源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Java类加载基本过程详细介绍

    Java类加载基本过程详细介绍

    这篇文章主要介绍了Java类加载基本过程详细介绍的相关资料,需要的朋友可以参考下
    2016-12-12
  • Apache Commons Math3探索之多项式曲线拟合实现代码

    Apache Commons Math3探索之多项式曲线拟合实现代码

    这篇文章主要介绍了Apache Commons Math3探索之多项式曲线拟合实现代码,小编觉得挺不错的,这里分享给大家,供需要的朋友参考。
    2017-10-10
  • 深入理解JAVA基础类库中对象Object类

    深入理解JAVA基础类库中对象Object类

    Object类是一个特殊的类,是所有类的父类,如果一个类没有用extends明确指出继承于某个类,那么它默认继承Object类。这里主要总结Object类中的两个:toString()与equals()方法
    2021-09-09
  • 项目依赖Springboot jar失败解决方案

    项目依赖Springboot jar失败解决方案

    这篇文章主要介绍了项目依赖Springboot jar失败解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • idea如何配置springboot热部署

    idea如何配置springboot热部署

    这篇文章主要介绍了idea如何配置springboot热部署问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 使用sharding-jdbc实现水平分表的示例代码

    使用sharding-jdbc实现水平分表的示例代码

    本文主要介绍了sharding-jdbc实现水平分表,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • 关于Socket的解析以及双方即时通讯的java实现方法

    关于Socket的解析以及双方即时通讯的java实现方法

    本篇文章主要介绍了关于Socket的解析以及双方通讯的java实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03

最新评论