Java中的自动拆装箱、装箱缓存

Java 专栏收录该内容
14 篇文章 0 订阅

前言

Java 是一种面向对象的编程语言,Java 中的类把方法与数据类型连接在一起,构成了自包含式的处理单元。但在 Java 中不能定义基本类型对象,为了能将基本类型视为对象处理,并能连接相关方法,Java 为每个基本数据类型都提供了包装类,如 int 型数值的包装类 Integer,boolean 型数值的包装类 Boolean 等。这样便可以把这些基本类型转换为对象来处理了。

在Java中包含了8种基本数据类型,与之相对应的还有8种包装类,他们之间的对应关系如下:

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

什么是自动拆装箱

Java中不能定义基本数据类型的对象,因此我们可以使用包装类,每种基本数据类型都有自己对应的包装类。
基本数据类型与包装类之间的转换过程就涉及到了自动拆装箱。

  • 基本数据类型转换为包装类的过程称作自动装箱
  • 包装类转换为基本数据类型的过程称作自动拆箱

自动拆装箱的实现原理

举一个栗子:

public class AutoBoxing {
    public static void main(String[] args) {
        int i = 10;
        //装箱
        Integer ii = i;
        //拆箱
        int iii = ii;
        }
}

上面的代码实际上就是Java中的语法糖,通过对.class文件进行反编译之后就可以看到代码的真面目:

public class AutoBoxing {
    public static void main(String[] arrstring) {
        int n = 10;
        Integer n2 = Integer.valueOf(n);
        int n3 = n2.intValue();
    }
}

从反编译后的代码可以看到,int类型到Integer的装箱过程是通过Integer.valueOf()实现,Integer到int的拆箱过程是通过intValue()实现。

刚好我们测试下其他七种数据类型的拆装箱过程是怎么样的,代码如下AutoBox.java

public class AutoBox {

    public static void main(String[] args) {
        Integer aa = 10;
        int aaa = aa;

        Byte bb = 20;
        byte bbb = bb;

        Short cc = 30;
        short ccc = cc;

        Long d = 40L;
        long dd = d;

        Float e = 50f;
        float ee = e;

        Double f = 60d;
        double ff = f;

        Character g = 'a';
        char gg = g;

        Boolean h = true;
        boolean hh = h;

    }
}

直接对AutoBox.java文件进行编译后,对AutoBox.class文件反编译分析,命令如下

//编译
javac AutoBox.java
//反编译分析
javap -c AutoBox.class

结果如下

Compiled from "AutoBox.java"
public class com.zhengql.practice.autoBox.AutoBox {
  public com.zhengql.practice.autoBox.AutoBox();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        10
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: astore_1
       6: aload_1
       7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
      10: istore_2
      11: bipush        20
      13: invokestatic  #4                  // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
      16: astore_3
      17: aload_3
      18: invokevirtual #5                  // Method java/lang/Byte.byteValue:()B
      21: istore        4
      23: bipush        30
      25: invokestatic  #6                  // Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
      28: astore        5
      30: aload         5
      32: invokevirtual #7                  // Method java/lang/Short.shortValue:()S
      35: istore        6
      37: ldc2_w        #8                  // long 40l
      40: invokestatic  #10                 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
      43: astore        7
      45: aload         7
      47: invokevirtual #11                 // Method java/lang/Long.longValue:()J
      50: lstore        8
      52: ldc           #12                 // float 50.0f
      54: invokestatic  #13                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
      57: astore        10
      59: aload         10
      61: invokevirtual #14                 // Method java/lang/Float.floatValue:()F
      64: fstore        11
      66: ldc2_w        #15                 // double 60.0d
      69: invokestatic  #17                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      72: astore        12
      74: aload         12
      76: invokevirtual #18                 // Method java/lang/Double.doubleValue:()D
      79: dstore        13
      81: bipush        97
      83: invokestatic  #19                 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
      86: astore        15
      88: aload         15
      90: invokevirtual #20                 // Method java/lang/Character.charValue:()C
      93: istore        16
      95: iconst_1
      96: invokestatic  #21                 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
      99: astore        17
     101: aload         17
     103: invokevirtual #22                 // Method java/lang/Boolean.booleanValue:()Z
     106: istore        18
     108: return
}

经过测试,其他7种基本数据类型到包装类的装箱拆箱原理都与int/Integer相同,
自动装箱都是通过包装类的valueOf()方法来实现的,
自动拆箱都是通过包装类对象的xxxValue()来实现的

什么时候用到自动拆装箱

  1. 赋值操作时
Integer a = 1;//Integer a = Integer.valueOf(1);//自动装箱
  1. 包装类之间运算时(±*/)
    Integer a = 1;
    Integer b = 2;
    int c = a + b;//int c = a.intValue() + b.intValue();//自动拆箱
  1. 比较运算时
    Integer a=1;
    boolean b = a==1;//boolean b = a.intValue()==1;自动拆箱
  1. 向集合中添加基本数据类型时
    List<Integer> list = new ArrayList<>();
    for (int i = 1; i < 10; i ++){
        list.add(i);//list.add(Integer.valueOf(i));自动装箱
    }
  1. 方法调用、参数返回时
public class AutoBox {

    private static int test(int i){
        return i + 1;
    }
    public static void main(String[] args) {
        Integer i = 1;
        int a = test(i);//int a = test(i.intvalue());自动拆箱
        Integer b = test(1);//Integer b = Integer.valueOf(test(1));//自动装箱
    }
}

装箱缓存

其实,在自动装箱过程中还存在一种缓存的操作,且看下面一道题:

public class AutoBoxTest {
    public static void main(String[] args) {
        Integer a = 30;
        Integer b = 30;

        if (a==b){
            System.out.println("a、b:内存地址相同");
        }else {
            System.out.println("a、b:不同的两个对象");
        }

        Integer c = 300;
        Integer d = 300;

        if (c==d){
            System.out.println("c、d:内存地址相同");
        }else {
            System.out.println("c、d:不同的两个对象");
        }
    }
}

这道题乍一看是不是觉得匪夷所思,怎么会有这种沙雕题目,两个对象类型用等号判断大小,很明显都是new出来的对象,肯定指向不同的内存地址啊,肯定不相等了。然鹅运行的结果如下:

a、b:内存地址相同
c、d:不同的两个对象

可以看到为什么同样的操作,c和d就符合判断逻辑,而a和b就偏偏指向同一个对象呢?

这是因为在自动装箱过程中,Integer对象通过使用相同的对象引用实现对象的缓存和重用。

那么问题又来了,既然有缓存操作,那为什么a、b有,c、d却没有呢?

来看一下Integer自动装箱的源码:

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

首先判断入参i是否处于[IntegerCache.low,IntegerCache.high]区间内,如果i值在区间内,则从缓存IntegerCache.cache中读取某一个值返回,反之直接new一个Integer对象,这说明触发缓存操作是根据i值的范围决定的

那这个范围又是多少呢?阅读该方法的注释:

    This method will always cache values in the range -128 to 127,
    inclusive, and may cache other values outside of this range.

此方法默认缓存[-128,127]范围内的值,但也可以缓存范围外的其他值,这里是因为区间右侧的IntegerCache.high是可配置的。

看到这里,终于明白,最开始的那道题目,为什么ab和cd的结果会完全不一样,是因为a、b的值在[-128,127]区间内,而c、d的值不在此范围内。

那么,既然Integer有缓存这个骚操作,那其他的包装类是不是也有呢?直接去看每个包装类的valueOf方法就可以知道了。

这里我就不贴源码了,查看后的结论是,其他的7种包装类中,所有的整数类型的类,在自动装箱时都有类似于Integer的这种缓存操作,只不过他们各自的触发情况不同,结果整理如下:

包装类缓存机制触发条件备注
ByteByteCache[-128,127]
ShortShortCache[-128,127]
IntegerIntegerCache[-128,127]最大值可配置
LongLongCache[-128,127]
Float--
Double--
Boolean--
CharacterCharacterCache[0,127]

总结

自动装箱和拆箱方便了我们开发人员,但是在使用自动拆装箱时也有很多翻车现场,最容易出现的就是空指针,所以在使用自动拆装箱时一定要防止空指针。

自动装箱过程中涉及到对象的创建等操作,如果在循环体中大量的拆装箱操作,势必会浪费资源,所以何时使用合理的使用自动拆装箱是尤为重要。

参考和感谢

Java中整型的缓存机制:https://www.hollischuang.com/archives/1174

  • 0
    点赞
  • 0
    评论
  • 5
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:游动-白 设计师:我叫白小胖 返回首页

打赏作者

LarsCheng

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值