提示

本文主要讲解 Java 的装箱与拆箱,以及 Java Integer Cache。@ermo

# Integer Cache

Java 基础类型为什么要有包装类型?

Java 本身就是面向对象的语言,有基础类型的包装类符合 Java 语言设计初衷。

其次,集合类内存储的也是 Object 对象,对基本类型并不支持。

下面代码编译时会提示错误。

public class Cache {
    public static void main(String[] args) {
        // Type argument cannot be of primitive type
        List<int> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
    }
}

什么是装箱?

装箱就是将 Java 基本类型转化为响应包装类的过程。

什么是拆箱?

拆箱就是将一个基本类型的对象转化为基本类型的过程。

下面是简单的自动装箱与拆箱的代码演示。

装箱是通过包装类的 valueOf 方法实现的。

拆箱是通过包装类的 xxxValue 方法实现的,比如 int 类型的拆箱操作就是通过 Integer.intValue 方法实现。

public class Cache {
    public static void main(String[] args) {
    	// unboxing
        Integer i = 1;
        // boxing
        int j = i;

        System.out.println(i);
        System.out.println(j);
    }
}

自动装箱与拆箱功能是 jdk1.5 的新特性,该功能是一项语法糖(Grammer Sugar),编译器在编译过程中自动将代码拆解。

可以手动编译 Cache 类进行查看验证。

javac Cache.java
java Cache

然后使用开源的反编译工具 JD-JUI (opens new window) 进行查看。

public class Cache {
  public static void main(String[] paramArrayOfString) {
    Integer integer = Integer.valueOf(1);
    int i = integer.intValue();
    System.out.println(integer);
    System.out.println(i);
  }
}

什么情况下会用到自动装箱?

Oracle 官网 (opens new window) 做出下面解释。

  • 作为参数传递给对应包装类对象的方法
  • 分配给对应包装类对象的变量

也就是下面两种情况

public class Cache {
    
    public static void boxing1(Integer i) {
        System.out.println(i);
    }
    
    public static void main(String[] args) {
        int i = 1;
        // 情况1
        boxing1(i);
        
        // 情况2
        Integer i1 = 100;
        System.out.println(i1); // 100
    }
}

什么情况下会使用到自动拆箱?

  • 作为参数传递给对应基本类型的方法
  • 分配给对应基本类型的变量

用代码表示就是下面两种情况。

public class Cache {

    public static void boxing1(int i) {
        System.out.println(i);
    }

    public static void main(String[] args) {
        Integer i = new Integer(1);
        // 情况1
        boxing1(i);

        // 情况2
        int i1 = Integer.valueOf(100);
        System.out.println(i1); // 100
    }
}

下面是可以使用自动装箱与拆箱的几种类型。

基本类型 包装类型
boolean Boolean
byte Byte
char Char
float Float
int Int
long Long
short Short
double Double

自动装箱和拆箱的优势有哪些?

Integer Cache 只会在装箱的过程中才会生效,通过包装类的构造器方法实例化的 Integer 对象不能使用 Integer Cache 功能。

只有在下列两种情况下会使用到 Integer Cache:

  • 整数值的范围在 -128~127
  • 通过自动装箱创建的 Integer 变量
public class Cache {

    public static void main(String[] args) {
        // unboxing
        Integer n1 = 1;
        Integer n2 = 1;

        System.out.println(n1 == n2); // true

        // constructor
        Integer n3 = new Integer(2);
        Integer n4 = new Integer(2);
        System.out.println(n3 == n4); // false
    }
}

上述代码中,为什么例1返回 true,例2返回 false?

我们知道 Java 中的 == 判断的是变量的引用地址,那么例1中的 n1n2 的内存地址是相同的,说明它们是同一个对象。

上文已经讲到自动装箱是通过 Integer.valueOf 方法实现的,那就直接看下 valueOf 的源代码。

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

代码实现很简单,如果当前值在一定范围内就之间返回 cache 对应的包装类,否则才会实例化一个新的 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;
            // ... 中间代码省略
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

可以看出,IntegerCache 是一个静态类,并且在静态代码块中进行类 cache[] 数组的实例化。

cache[] 数组内的值取值范围正是 -128~127。

这也解释了为什么例1返回 true 而例2 返回 false。

-128~127 是使用最频繁的数值,Java 提供这一特性是为了提高系统的性能。

在实际开发过程中,开发人员应该强制使用 n1.equals(n2) 来进行整数包装类对象的比较,这样可以有效避免复杂业务场景中的简单错误。

Integer 覆写了 Object 类中的 equals 方法,调用 equals 方法不再比较包装类对象的内存地址,而是比较二者的数值。

下面是 Integer 的源代码。

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

放一个常用到的整数值比较的场景,供读者参考。

    public static void main(String[] args) {
        // cache data: -128~127
        Integer n1 = 10;
        Integer n2 = 10;
        System.out.println(n1 == n2); // true

        Integer n3 = new Integer(10);
        Integer n4 = new Integer(10);
        System.out.println(n3 == n4); // false

        Integer n5 = 10;
        Integer n6 = new Integer(10);
        System.out.println(n5 == n6); // false

        Integer n7 = 128;
        Integer n8 = 128;
        System.out.println(n7 == n8); // false

        // auto unboxing
        int n9 = 10;
        Integer n10 = new Integer(10);
        System.out.println(n9 == n10); // true
    }