原创

Java的自动装箱与拆箱原理探讨

什么是自动装箱拆箱

自动装箱拆箱 Autoboxing and unboxing

装箱就是自动将基本数据类型转换为包装器类型;
拆箱就是自动将包装器类型转换为基本数据类型。

装箱拆箱过程是自动执行的。

//自动装箱
Integer total = 99;

//自动拆箱
int totalprim = total;

需要装箱拆箱的类型有哪些?

通过源代码理解执行过程

执行代码

package com.yueny.study.jdk.autoboxing;

/**
 * @author fengyang <deep_blue_yang@126.com>
 * @date 2020/9/30  10:54 上午
 * @title: IntegerAutobox
 * @projectName algorithm
 * @description:
 */
public class IntegerAutobox {
    public static void main(String[] args) {
        //自动装箱
        Integer totalInteger = 99;
        //自定拆箱
        int totalIntegerprim = totalInteger;
    }
}

编译得到class文件后,反编译class文件

javap -c IntegerAutobox.class

反编译class内容如下:

Compiled from "IntegerAutobox.java"
public class com.yueny.study.jdk.autoboxing.IntegerAutobox {
  public com.yueny.study.jdk.autoboxing.IntegerAutobox();
    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        99
       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: return
}
代码 系统执行
Integer totalInteger = 99; Integer totalInteger = Integer.valueOf(99); 自动装箱
int totalIntegerprim = totalInteger; int totalIntegerprim = totalInteger.intValue(); 自动拆箱

Integer 的装拆箱

装箱

1、首先看Integer.valueOf函数

    public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }
    public static Integer valueOf(int i) {
        if (i >=-128 && i <= 127)
            return IntegerCache.cache[i + (128)];
        return new Integer(i);
    }

它会首先判断 i 的大小:如果 i 大于等于-128并且小于等于127,则执行 IntegerCache.cache[i + 128]。否则,就创建一个Integer对象。

2、再看Integer的构造函数

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;

    public Integer(int value) {
        this.value = value;
    }
    public Integer(String s) throws NumberFormatException {
        this.value = parseInt(s, 10);
    }

构造函数里面定义了一个value变量,创建一个Integer对象,就会给这个变量初始化。第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。

3、再看 IntegerCache.cache[i + 128]

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() {}
}

在jdk1.8中,IntegerCache 为 Integer 的内部私有类。

IntegerCache中有3个final类型的变量:

    low:-128(一个字节能够表示的最小值); 
    high:127(一个字节能够表示的最大值),JVM中设置的属性值(通过-XX:AutoBoxCacheMax=设置)二者取大值,再和Integer.MAX_VALUE取小值; 
    cache:在静态块中初始化为由区间[low, high]上的所有整数组成的升序数组。

经过一系列的运算,static final Integer cache[] 相当于 static final Integer[] SMALL_VALUES = new Integer[256];
它是一个静态的Integer数组对象,也就是说最终valueOf返回的都是一个Integer对象。

因此,可以初步得到一个简单的结论:因为装箱的过程会创建对应的对象,因此会额外的消耗内存,所以装箱的过程会增加内存的消耗,进而对性能会存在影响(可能影响比较微量)。

拆箱

4、intValue函数

    public int intValue() {
        return value;
    }

拆箱很简单,直接返回value值。

Integer 装拆箱总结

  • 在 i >= 128 || i < -128时, 会创建不同的新对象, 分配不同的地址和内存空间。
  • 在 -128 <= i < 128 会根据i的值返回已经创建好的指定的对象(IntegerCache 缓存)。

举例

code

    private static void testInteger(){
        Integer i1 = 100;
        Integer i2 = 100;

        Integer i3 = 200;
        Integer i4 = 200;

        Assert.isTrue(i1 == i2, "如果此处报错说明返回了 false。");
        System.out.println(i1 == i2);  //true

        Assert.isTrue(i3!=i4, "如果此处报错说明返回了 false");
        System.out.println(i3==i4);  //false
    }

输出结果:

true
false

说明:

  • i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿IntegerCache里面的同一个对象,引用到了同一个Integer对象,所以它们肯定是相等的。
    • i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是说它们会分别创建两个不同的对象,所以它们肯定不等。

基本类型装拆箱汇总

其实对于Java的基本类型装拆箱而言,都是大同小异的。
装箱调用的都是 valueOf 方法;拆箱调用的均是 xxxValue 方法。

我们可以总结归纳为如下几种情形:

自动装拆箱调用方法

基本类型 常规类型 自动装箱调用方法 自动拆箱调用方法
Integer int Integer.valueOf .intValue()
Short short Short.valueOf .shortValue()
Byte byte Byte.valueOf .byteValue()
Character char Character.valueOf .charValue()
Long long Long.valueOf .longValue()
Double double Double.valueOf .doubleValue()
Float float Float.valueOf .floatValue()

各类型的相同值范围

类型 相同对象范围(缓存区间) 不同对象范围(非缓存区间) 缓存区间是否可变 代码段
Integer [-128, 127] i >= 128 或者 i < -128 上限可设置 --
Short [-128, 127] s >= 128 或者 s < -128 不可变 --
Character [0, 127] c >= 128 不可变 if (c <= 127) return CharacterCache.cache[(int)c];
Long [-128, 127] l >= 128 或者 l < -128 不可变 --
Byte [-128, 127] b >= 128 或者 b < -128 不可变 --
Double -- ALL -- --
Float -- ALL -- --
Boolean {true, false} -- 不可变 --

分门别类

Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的:存在缓存区间的设置。
Double派别:Double、Float的valueOf方法的实现是类似的:不存在缓存区间,每次都返回不同的对象。
Boolean: Boolean没有缓存的概念,所有的返回值全部为固定值 TRUE, FALSE。

Boolean 装拆箱代码片

   /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    public boolean booleanValue() {
        return value;
    }

回首望月

通过上面的分析,我们可以得到几点注意事项。

  • 当用“Boolean bool = true”的自动装箱方式定义变量的时候,这两个变量其实指向的都是Boolean类型的缓存池中的那个值为 TRUE 的对象,所以用它们当做同步锁,其实是用的同一把锁,必然会出现竞争等待。
  • 当我们使用自动装箱机制初始化变量的时候,就相当于告诉JAVA这里需要一个对象而不是告诉JAVA这里需要一个新的对象。当我们需要一个新的对象的时候,那就自己new出来。
  • 因为基本类型对应的包装器都是不可变类,它们的缓存区间一旦初始化,里面的值就无法再改变,所以在JVM运行过程中,所有的基本类型包装器的缓存池都是不变的
  • 当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算(也就是值的运算)。
    • 当进行==、+、-、*、/运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象;而如果其中有一个是基本类型时则比较的是数值(即会触发自动拆箱的过程)。
Integer num1 = 400;  
int num2 = 400;  
System.out.println(num1 == num2);   //true
Integer num1 = 100;  
int num2 = 100;  
Long num3 = 200l;  
System.out.println(num1 + num2);  //200
System.out.println(num3 == (num1 + num2));  //true

System.out.println(num3.equals(num1 + num2));  //false
  • .equals 运算为 false 原因:必须满足两个条件才为true:
    • 1、类型相同
    • 2、内容相同
      上面返回false的原因就是类型不同。
@Override
public boolean equals(Object o) {
     return (o instanceof Long) && (((Long) o).value == value);
}
正文到此结束
广告是为了更好的提供数据服务
本文目录