装箱 就是将基本的数据类型(比如int
)封装成对象类型(比如Integer
)。拆箱 就是将对象类型拆包装为基本类型。
装箱和拆箱操作为我们提供了很多便利。
- 包装类,如
Integer
给我们提供的友好的API
方法,使我们可以快速完成数据类型之间的转换。Java
是面向对象的,我们在Java
中处理的绝大多数都是对象,在类似Map
集合类等很多场合中,我们需要放入Integer
这种对象类型。
很多情况下,编译器为我们自动完成了基本数据类型的拆箱和装箱操作。
public class TestInteger{
public static void main(String[] args){
int a1 = 127;
//auto-boxing
Integer i = a1;
//auto-unboxing
int b1 = i;
}
}
下面,我们来看一段代码:
public class TestInteger{
public static void main(String[] args){
int a1 = 12;
//auto-boxing
Integer number1 = a1;
Integer number2 = a1;
System.out.println("number1==number2:"+ (number1==number2));
//out:number1==number2:true
int b1=128;
Integer number3 = b1;
Integer number4 = b1;
System.out.println("number3==number4:"+ (number3==number4));
//out:number3==number4:false
}
}
通过编译,我们发现Integer
类型在auto-boxing
操作时,-128到127之间的int
类型整数封装之后的对象是同一个,这个范围之外的int
类型整数封装之后对象是不同的。通过后面的分析,我们可以知道:
-128到127这个范围,-128是固定的,上限127是我们可以通过Jvm参数配置的。
为什么会这样呢?我们通过这段程序的字节码分析这段程序编译的细节:
Microsoft Windows [版本 10.0.15063]
(c) 2017 Microsoft Corporation。保留所有权利。
D:\JavaProject\SuperMarket\bin\com\javaexc\service>javap -c Test.class
Compiled from "Test.java"
public class com.javaexc\service.Test {
public com.javaexc\service.Test();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 12
2: istore_1
3: iload_1
4: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
7: astore_2
8: iload_1
9: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: astore_3
13: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
16: new #28 // class java/lang/StringBuilder
19: dup
20: ldc #30 // String number1==number2:
22: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
25: aload_2
26: aload_3
27: if_acmpne 34
30: iconst_1
31: goto 35
34: iconst_0
35: invokevirtual #35 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
38: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
41: invokevirtual #43 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: sipush 128
47: istore 4
49: iload 4
51: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
54: astore 5
56: iload 4
58: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
61: astore 6
63: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
66: new #28 // class java/lang/StringBuilder
69: dup
70: ldc #48 // String number3==number4:
72: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
75: aload 5
77: aload 6
79: if_acmpne 86
82: iconst_1
83: goto 87
86: iconst_0
87: invokevirtual #35 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
90: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
93: invokevirtual #43 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
96: return
}
通过观察4、9、51、58这四行字节码我们发现,编译器调用
Integer.valueOf()
帮助我们完成自动装箱操作。
我们进一步阅读Integer.class
文件,分析Integer.valueOf()
实际上完成的工作:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
当输入参数i
满足i >= IntegerCache.low && i <= IntegerCache.high
这个范围时,return
的是IntegerCache.cache[]
这个数组里的元素,否则return
一个新的Integer
对象。
那么IntegerCache.low
和 IntegerCache.high
以及IntegerCache.cache[]
分别是什么呢?我们继续看代码:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the -XX:AutoBoxCacheMax=<size> option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
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) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low));
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
我们看到IntegerCache.low
和 IntegerCache.high
以及IntegerCache.cache[]
都是static final
修饰的,IntegerCache.low
固定是-128,IntegerCache.high
可以通过设置属性来修改,默认是127。
-
结论1:
-
int
类型整数自动装箱时,当值在上述范围内时,返回的是IntegerCache.cache[]
这个数组里的同一个元素。
这也就是-128到127之间的int
类型整数自动封装成同一个对象的原因。这里的IntegerCache
就是用来处理Integer
自动装箱操作的。
在自动装箱时,当值在
[–128,127]
之间时,引用指向的都是内存中的同一个对象。如果超过这个范围,每次装箱都会new()
一个新的对象。这样便加大了对简单数字的重复使用,在某些场景下可以优化我们代码的性能。
-
结论2:
-
值得说明的是,上述情况只有在我们使用自动装箱时,编译器自动调用
Integer.valueOf()
才会出现,如果我们采用new()
来创建对象,每一次new()
的对象都是一个新的对象。