提示
本文主要讲解 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) (opens new window) 特性是在 jdk1.5 的时候新增的。
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中的 n1
和 n2
的内存地址是相同的,说明它们是同一个对象。
上文已经讲到自动装箱是通过 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
}