Loading

[Java基础]拆箱装箱

在介绍本期文章内容之前,让我们先来看一小段代码:

int a = 10;
Integer b = 10;
if(b == a){
	System.out.println("相等");
}

执行结果应该大家是毋庸置疑的,10等于10,自然会输出相等。但是有一个问题,a明明是int类型,而b则是Integer类型。两个明显是不同类型的对象,为什么能够相等呢?这就涉及到我们这次要介绍的内容:拆箱、装箱
简介
首先,简单介绍下装箱、拆箱的含义:

装箱,指基础类型(int、double等)转换成包装类型(Integer、Double等)的过程
拆箱,顾名思义同装箱相反,是从包装类型转换到基础类型的过程。

在JDK1.5以前,还不存在装箱、拆箱的概念。因此,对于基本类型同包装类型的转换,基本只能通过代码手写实现。
ini 代码解读复制代码     Boolean a = Boolean.valueOf(true);
     Integer b = Integer.valueOf(10);

然而,懒惰是推动人类社会进步的动力。时间一长了,大家就发现:
“明明这两个类型都是表达相似含义的东西,有没有什么办法把这两个的转换隐藏起来呢?这样我就不用写着么多代码了呀”
实现原理
结合上述对于拆箱装箱的介绍,我们不难分析出来,要实现拆箱和装箱,至少需要考虑两点:

什么时候做拆箱装箱?
怎么做拆箱装箱?

啥时候拆箱装箱
首先明确一点,装箱和拆箱是指发生在基本类型与包装类型之间的。如果一个类根本都不存在包装类型 or 基本类型,那么肯定是不会发生拆箱装箱的。在Java中,同时存在基本类型和包装类型的有如下几个:

基本类型包装类型booleanBooleanbyteByteintIntegershortShortcharCharacterlongLongfloatFloatdoubleDouble
那么在知道了有哪些类型存在包装类和基本类后。我们还需要知道装箱和拆箱触发的时机。简单来说主要分为以下几个:
1、赋值
2、调用方法
3、数值计算
设值,这个主要就是讲的将基本类型的数值赋值给包装类型,如下代码所示:
ini 代码解读复制代码     Integer b = 10;

调用方法,主要指的是方法的入参为包装类型,传入的实际上是基本类型的情况,以Integer类型为例子,调用方法产生装箱拆箱的情况如下所示:

public static void main(String[] args) {
         //产生装箱、拆箱
         transform(1);
     }

     public static void transform(Integer b){
             System.out.println(b);
     }

最后一种情况,主要指的是基本类型同包装类型做计算的时候会发生拆箱。具体例子如下所示:
ini 代码解读复制代码     public static void main(String[] args) {
         Integer b = 10;
         int ab = 20;
         int i = b + ab; // b调用拆箱的方法
    }

怎么做拆箱装箱
装箱
在明确了拆箱装箱的时机以后,就是考虑如何实现转化了。实际上,在由编译.java的过程中,编译器会将特定的包装类型调用对应的转化方法,下面以具体的代码为例子:
ini 代码解读复制代码     Boolean a = true;
     Integer b = 10;

通过将上述代码的.class文件进行反编译(调用代码javap -c xxx.class),可以得到如下的反编译代码:
yaml 代码解读复制代码 public class com.example.demo.DemoApplication {
   public com.example.demo.DemoApplication();
     Code:
        0: aload_0
        1: invokespecial #1                  // Method java/lang/Object.""😦)V
        4: return
 ​
   public static void main(java.lang.String[]);
     Code:
        0: iconst_1
        1: invokestatic  #2                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
        4: astore_1
        5: bipush        10
        7: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       10: astore_2
       11: goto          17
       14: astore_1
       15: aload_1
       16: athrow
       17: return
     Exception table:
        from    to  target type
            0    11    14   Class java/lang/Throwable
 ​
   static {};
     Code:
        0: ldc           #6                  // class com/example/demo/DemoApplication
        2: invokestatic  #7                  // Method org/slf4j/LoggerFactory.getLogger:(Ljava/lang/Class;)Lorg/slf4j/Logger;
        5: putstatic     #8                  // Field LOGGER:Lorg/slf4j/Logger;
        8: return
 }

可以看到,装箱实际上调用的分别是Boolean.valueOf和Integer.valueOf的代码。这里我们具体以Integer.valueOf展开来看具体的实现:
arduino 代码解读复制代码     public static Integer valueOf(int i) {
         return new Integer(i);
    }

valueOf最简单的实现就是直接根据传入的int类型生成一个Integer对象。但是仔细想想,这样真的好吗?假设我们有一个计算密集型的系统,每天都会执行许多次的对象创建。由此会带来频繁的GC,对于JVM会产生较大的压力。
这也是为什么IDEA针对一些装箱的操作,会给出相应的warning提示信息,会建议将Integer类型转换成基本类型(即int)。

因此,Integer类中自然也对这种情况做了考虑。Integer类在实现的时候,会将常用范围的数据缓存下来(默认的范围是[-128,127]),当传进来的数字处于这个范围的时候,就直接获取缓存好的数据值,减少对象的生成,进而减少GC的次数。实现代码如下所示:
arduino 代码解读复制代码     public static Integer valueOf(int i) {
         if (i >= IntegerCache.low && i <= IntegerCache.high)
             return IntegerCache.cache[i + (-IntegerCache.low)];
         return new Integer(i);
    }

需要注意的是,并非所有包装类都能够实现缓存逻辑。例如Double、Float等浮点数类型,由于小数在一个范围内是无穷多的。因此这些都不会做缓存处理。
拆箱
说完装箱,我们简单聊聊拆箱。拆箱的实现其实也并不复杂,主要就是调用包装对象里的拆箱方法。以Integer类为例子,其主要调用的就是Integer.intValue方法,具体源代码如下所示:
csharp 代码解读复制代码     public int intValue() {
         return value;
    }

作者:笑傲菌
链接:https://juejin.cn/post/7175725853936975932
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @   Duancf  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示