深入理解Java的装箱和拆箱

深入理解Java的装箱和拆箱

引言

原始类型和其对应的包装类之间的装箱和拆箱都是“底层”帮忙做的。这是一个模糊的概念,本篇试图说明以下几个问题:

  • WHAT (什么是装拆箱)
  • WHEN(什么时候发生)
  • WHO (谁来装拆)
  • HOW(怎样装拆)

一个栗子

这个栗子非常好的涵盖了使用装拆箱要注意的方面。看官可以模拟执行一下,后面会讲正确答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.zhaoyangwoo.utils;
/**
* Created by john on 16/10/12.
*/
public class Test {
public static void main(String[] args) {
//装拆箱时机1:赋值
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));
autoBoxingMethod(1);
//装拆箱时机2:参数传递
unBoxingMethod(a);
performence();
}
public static void autoBoxingMethod(Integer ainteger) {
System.out.println("调用autoBoxingMethod会装箱");
}
public static void unBoxingMethod(int aint) {
System.out.println("调用unBoxingMethod会拆箱");
}
public static void performence() {
Integer sum = 0;
//int sum = 0; 有什么不同吗?
for (int i = 0; i < 100; i++) {
sum += i;
}
}
}

WHAT

所谓装箱就是将原始类型转换成引用类型,而拆箱就是将引用类型转换成原始类型。对应关系如下:

Primitive type Wrapper class
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

WHEN

装箱和拆箱只会发生在两个地方:1.赋值 2.函数调用的参数传递,见栗子中的注释。

WHO

装拆箱的操作由编译器在将java文件编译成class文件时完成转换(编译期)

HOW

重点讲一下编译器怎样执行转换操作,需要查看class文件的字节码。

1
javap -c Test.class

任你隐藏的多深,有了这面“照妖镜”,一切都明了了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class com.zhaoyangwoo.utils.Test {
public static void main(java.lang.String[]);
Code:
//Integer a = 1;
0: iconst_1
//调用Integer.valueOf() -->装箱
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
//Integer b = 2;
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
//Integer c = 3;
10: iconst_3
11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: astore_3
//Integer d = 3;
15: iconst_3
16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: astore 4
//Integer e = 321;
21: sipush 321
24: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: astore 5
//Integer f = 321;
29: sipush 321
32: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
35: astore 6
//Long g = 3L;
37: ldc2_w #3 // long 3l
//调用Long.valueOf() -->装箱
40: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
43: astore 7
//System.out.println(c == d);
45: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
48: aload_3
49: aload 4
51: if_acmpne 58
54: iconst_1
55: goto 59
58: iconst_0
59: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
//System.out.println(e == f);
62: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
65: aload 5
67: aload 6
69: if_acmpne 76
72: iconst_1
73: goto 77
76: iconst_0
77: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
//System.out.println(c == (a + b));
80: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
83: aload_3
84: invokevirtual #8 // Method java/lang/Integer.intValue:()I
//调用Integer.intValue() -->拆箱
87: aload_1
88: invokevirtual #8 // Method java/lang/Integer.intValue:()I
91: aload_2
92: invokevirtual #8 // Method java/lang/Integer.intValue:()I
95: iadd
96: if_icmpne 103
99: iconst_1
100: goto 104
103: iconst_0
104: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
//System.out.println(c.equals(a + b));
107: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
110: aload_3
111: aload_1
112: invokevirtual #8 // Method java/lang/Integer.intValue:()I
115: aload_2
116: invokevirtual #8 // Method java/lang/Integer.intValue:()I
119: iadd
120: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
123: invokevirtual #9 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
126: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
//System.out.println(g == (a + b));
129: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
132: aload 7
134: invokevirtual #10 // Method java/lang/Long.longValue:()J
137: aload_1
138: invokevirtual #8 // Method java/lang/Integer.intValue:()I
141: aload_2
142: invokevirtual #8 // Method java/lang/Integer.intValue:()I
145: iadd
146: i2l
147: lcmp
148: ifne 155
151: iconst_1
152: goto 156
155: iconst_0
156: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
//System.out.println(g.equals(a + b));
159: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
162: aload 7
164: aload_1
165: invokevirtual #8 // Method java/lang/Integer.intValue:()I
168: aload_2
169: invokevirtual #8 // Method java/lang/Integer.intValue:()I
172: iadd
//函数调用参数传递-->装箱
173: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
176: invokevirtual #11 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
179: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
//autoBoxingMethod(1);
182: iconst_1
183: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
186: invokestatic #12 // Method autoBoxingMethod:(Ljava/lang/Integer;)V
//unBoxingMethod(a);
//函数调用参数传递-->拆箱
189: aload_1
190: invokevirtual #8 // Method java/lang/Integer.intValue:()I
193: invokestatic #13 // Method unBoxingMethod:(I)V
//performence();
196: invokestatic #14 // Method performence:()V
199: return
public static void autoBoxingMethod(java.lang.Integer);
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #15 // String 调用autoBoxingMethod会装箱
5: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void unBoxingMethod(int);
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #17 // String 调用unBoxingMethod会拆箱
5: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}

说明:字节码可以更好的理解装拆箱:将原始类型赋值给引用类型会调用valueOf()装箱,而将引用类型赋值给原始类型会调用intVlaue()拆箱,在参数传递过程同理。

注意事项

性能

栗子里面也已经可以看到,不理解装拆箱可能会不经意间对性能造成很大的影响。像栗子中的代码,因为sumInteger类型,所以每次执行sum += i;都进行了两次装拆:

  1. sum拆箱取得其值,跟i相加.
  2. 将相加的结果装箱赋值给sum

如果将sum的类型设置为int,那么这里就不会有这方面的性能损耗。这点是需要注意的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void performence() {
Integer sum = 0;
//int sum = 0; 有什么不同吗?
for (int i = 0; i < 100; i++) {
sum += i;
}
}
//字节码
public static void performence();
Code:
//Integer sum = 0;
0: iconst_0
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_0
//int i = 0;
5: iconst_0
6: istore_1
7: iload_1
8: bipush 100
//i < 100;
10: if_icmpge 29
//sum += i;
13: aload_0
//将sum拆箱
14: invokevirtual #8 // Method java/lang/Integer.intValue:()I
17: iload_1
//sum+i;
18: iadd
//步骤18的结果装箱
19: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: astore_0
// i++;
23: iinc 1, 1
26: goto 7
29: return

相等

上面的栗子有6个相等的式子,下面一一分析结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//true 没算术运算,不拆箱,同时jvm会缓存-127~127的Integer对象
System.out.println(c == d);
//false 没算术运算,不拆箱,而321又不在缓存的范围内
System.out.println(e == f);
//true 算术运算,需要拆箱,哪怕都是Integer类型
System.out.println(c == (a + b));
//true equal比较的是对象的值
System.out.println(c.equals(a + b));
//true 这个要特别注意,根据字节码理解,先加载Long类型的g,拆箱成long类型的数值,
//再加载Integer类型的a,拆箱成int类型的数值,再加载Integer类型的b,
//拆箱成int类型的数值,执行两个int的+操作,再将结果强制转换成long类型,
//在跟g的数值比较。
System.out.println(g == (a + b));
//false 这个也要特别注意,equal方法不执行类型转换,类型不一致一定是false
System.out.println(g.equals(a + b));

好,在想一下System.out.println(g == c);输出什么?==并未遇到算术运算,自然不会拆箱,==比较的是”相同类型对象”的地址,因此这条语句编译会报错。

Error:(16, 30) java: 不可比较的类型: java.lang.Long和java.lang.Integer

总结:包装类的==运算不遇到算术运算不会自动拆箱(比较指针);equal不执行类型转换。

参考

作者: wuzhaoyang(John)
出处: http://wuzhaoyang.me/
因为作者水平有限,无法保证每句话都是对的,但能保证不复制粘贴,每句话经过推敲。希望能表达自己对于技术的态度,做一名优秀的软件工程师。