专栏原创出处:github-源笔记文件 (opens new window) ,github-源码 (opens new window),欢迎 Star,转载请附上原文出处链接和本声明。
Java 核心知识专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java 核心知识 (opens new window)
# 一、为什么要自动装箱拆箱
基础数据类型不是对象,或者说基础数据类型不是 Object 的子类。所以不支持基础数据类型与 Object 之间的强制转型。
JDK 5 后出现了泛型,但是泛型类型在编译时被擦除了,在需要的时候进行强制转换操作,但是基础数据类型与 Object 之间无法强制转型,所以引入了基本数据类型的包装类,Integer、Long...等。
如果不支持自动装箱拆箱,我们平时写代码就需要 Integer i = new Integer(10);
,比较繁琐,因此出现了语法糖自动装箱拆箱。
# 二、自动装箱拆箱实现
- 装箱过程是通过调用包装器的 valueOf 方法实现的
- 拆箱是通过调用包装器的 xxxValue 方法实现的。(xxx 代表对应的基本数据类型)
编译器在编译期间根据条件判断自动插入了装箱拆箱的字节码。
# 三、什么时候装箱
- 在基本类型的值赋值给包装类型时触发
Integer a = 1;
// 最终字节码调用 Integer a = Integer.valueOf(1);
- 包装器 equals(Object o) 方法,传入的基本类型自动装箱
Integer a = 1;
a.equals(1) // 最终字节码调用 a.equals(Integer.valueOf(1));
包装器的 equals 方法先判断对象类型是否一致,然后拆箱后用基础数据类型比较。比如下面的 Integer.equals 源码
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
比如:Long 与 Integer 比较时,就算数值相等,但是对象类型不相等时直接返回 false。
# 四、什么时候拆箱
- 包装类型赋值给基本类型时触发
Integer a = 1; // a 是 Integer 类型
int b = a; // 将 Integer 类型赋值给 int 类型,触发拆箱
- 运算符运算时触发
Integer a = 1;
Integer b = 2;
System.out.print(a * b); //这时 a*b 在编译成 a.intValue() * b.intValue();
- == 运算时,两边类型一个为包装类一个为基本类型时触发
比如:
如果比较的类型都是 Integer 类型,则不会拆箱,因为 == 也可以直接比较对象。
如果比较的类型一个是 int 类型,另一个是 Integer 类型,则会触发拆箱,然后对两个 int 值进行比较。
Integer b = 321;
int c = 321;
System.out.println(a == c); // a 拆箱为 int 值进行比较(Integer.intValue)
# 四、自动装箱拆箱的字节码分析
Integer a = 321;
Integer b = 321;
int c = 321;
--------------- 赋值过程关键字节码 ------------
0 sipush 321
3 invokestatic #2 <java/lang/Integer.valueOf> Integer a = 321; 装箱
7 sipush 321
10 invokestatic #2 <java/lang/Integer.valueOf> Integer b = 321; 装箱
14 sipush 321
17 istore_3 // int c = 321; 直接保存至局部变量表,无需其他操作
--------------- System.out.println(a == b); == 运算时判断类型一致性------------
21 aload_1 // 加载 a
22 aload_2 // 加载 b
23 if_acmpne 30 (+7) // 比较栈顶两引用型数值, 当结果不相等时跳转
--------------- System.out.println(a * b); 运算符运算时触发------------
37 aload_1 // 加载 a
38 invokevirtual #5 // a * b 运算中的 a 拆箱(Integer.intValue)
41 aload_2 // 加载 b
42 invokevirtual #5 // a * b 运算中的 b 拆箱(Integer.intValue)
45 imul // 将栈顶两 int 型数值相乘并将结果压入栈顶
--------------- System.out.println(a == c); == 运算时判断类型不一致时拆箱------------
52 aload_1 // 加载 a
53 invokevirtual #5 // a == c 运算中的 a 拆箱(Integer.intValue)
56 iload_3 // 加载 c
57 if_icmpne 64 (+7)// 比较栈顶两 int 型数值大小, 当结果不等于 0 时跳转
--------------- System.out.println(a * c); 运算符运算时触发------------
71 aload_1 // 加载 a
72 invokevirtual #5 // a * c 运算中的 a 拆箱(Integer.intValue)
75 iload_3 // 加载 c
76 imul // 将栈顶两 int 型数值相乘并将结果压入栈顶
--------------- int d = a; 包装类型赋值给基本类型时触发------------
80 aload_1
81 invokevirtual #5 // int d = a; 中的 a 拆箱(Integer.intValue)
84 istore 4
# 五、包装类型的缓存
为了减少对象创建。
CharacterCache/ByteCache/IntegerCache/LongCache/ShortCache
(除了 Float、Double)分别缓存了他们对于包装类 -128~127 之间的对象数据。
因此在使用 == 比较这些对象时会返回 true。
# 六、通过字节码分析跨数据类型比较过程
示例代码如下:
Integer a1 = 3;
long b1 = 3;
boolean b = (a1 == b1);
关键字节码如下,我们可以发现在过程中 a1 进行了装箱后拆箱操作(性能问题)。 对于不同数据类型的比较采用向宽数据范围强转后比较。
Code:
0: iconst_3 // 将 3 压入栈顶
1: invokestatic #2 // 装箱 Integer.valueOf(3)
4: astore_1 // 放入槽位 1
5: ldc2_w #3 // 将 long b1 = 3 常量值从常量池中推送至栈顶
8: lstore_2 // 将 long b1 = 3 常量值放入槽位 2
9: aload_1 // 加载 Integer = 3 的引用
10: invokevirtual #5 // 拆箱 ,Integer.intValue
13: i2l // 将栈顶 int 型数值 3 强制转换为 long 型数值并将结果压入栈顶
14: lload_2 // 将 long b1 = 3 压入栈顶
15: lcmp // 比较栈顶两 long 型数值大小, 并将结果 (1, 0 或-1) 压入栈顶
16: ifne 23 // 当栈顶 int 型数值不等于 0 时跳转
LocalVariableTable:
Start Length Slot Name Signature
0 27 0 args [Ljava/lang/String;
5 22 1 a1 Ljava/lang/Integer;
9 18 2 b1 J
26 1 4 b Z
# 总结
为了让基础数据类型能与 Object 进行强转引入了包装类型
基础类型与包装类型的转化可以是隐式的(自动有编译期间字节码完成)
什么时候装箱?
- 在基本类型赋值给包装类型时触发
- 包装器 equals 方法,传入的基本类型自动装箱(equals 先判断对象类型,在拆箱为基本数据类型比较)
什么时候拆箱?
- 包装类型赋值给基本类型时触发
- 运算符运算时触发
- == 运算时,两边类型一个为包装类一个为基本类型时触发
包装类除了 Float、Double ,其余包装类缓存了 -128~127 之间的对象,== 比较时返回 true。
不同数据类型比较时,字节码会默认向宽精度转化,然后进行比较
尝试解答:
public static void main(String[] args) {
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(c.equals(d));
System.out.println(e == f);
System.out.println(e.equals(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));
Integer a1 = 3;
long b1 = 3;
System.out.println(a1 == b1);
}