想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。
本文基于 Java 14
在JDK1.5引入自动装箱/拆箱,让开发更高效。自动装箱时编译器调用valueOf()
将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()
这类的方法将对象转换成原始类型值。
自动装箱是将 boolean 值转换成 Boolean 对象,byte 值转换成 Byte 对象,char 转换成 Character 对象,float 值转换成 Float 对象,int 转换成 Integer,long 转换成 Long,short 转换成 Short,自动拆箱则是相反的操作。
public static void main(String[] args) {
Long v = 6L;
long a = v;
if (a > v) {
}
}
通过javap -c
命令查看:
0 ldc2_w #68 <6>
3 invokestatic #66
6 astore_1
7 aload_1
8 invokevirtual #70
11 lstore_2
12 lload_2
13 aload_1
14 invokevirtual #70
17 lcmp
18 ifle 21 (+3)
21 return
可以看到,调用Long.valueOf
自动封箱,调用Long.longValue
自动拆箱。
public static void main(String[] args) {
Long v = 6L;
long a = v;
}
通过javap -c
命令查看:
0 ldc2_w #68 <6>
3 invokestatic #66
6 astore_1
7 aload_1
8 invokevirtual #70
11 lstore_2
12 return
可以看到,调用Long.valueOf
自动封箱,调用Long.longValue
自动拆箱。
public static void main(String[] args) {
Long v = 6L;
test1(v);
long a = v;
test2(a);
}
private static void test1(long v) {
}
private static void test2(Long v) {
}
通过javap -c
命令查看main
方法:
0 ldc2_w #68 <6>
3 invokestatic #66
6 astore_1
7 aload_1
8 invokevirtual #70
11 invokestatic #71
14 aload_1
15 invokevirtual #70
18 lstore_2
19 lload_2
20 invokestatic #66
23 invokestatic #72
26 return
调用方法前,发生了自动拆箱与自动装箱。
public static void main(String[] args) {
Long v = 6L;
test1(v);
long a = v;
test1(a);
}
private static void test1(long v) {
}
private static void test1(Long v) {
}
通过javap -c
命令查看main
方法:
0 ldc2_w #68 <6>
3 invokestatic #66
6 astore_1
7 aload_1
8 invokestatic #70
11 aload_1
12 invokevirtual #71
15 lstore_2
16 lload_2
17 invokestatic #72
20 return
这次调用方法前,并没有发生自动拆箱与自动装箱。
由于自动封箱拆箱需要额外的操作,运算必须转化为原始类型,所以在**运算过程中,使用原始类型。存储数据的时候,用封装类型,**因为原始类型有默认值,我们有时候想用null
代表这个数据不存在。
例如下面的代码,这个封箱就是没有必要的,会浪费性能:
Long l = 0L;
for(int i = 0; i < 50000; i++) {
l += 1L;
}
对于三目运算符,比如冒号表达式,如果有原始类型,则会发生自动拆箱。
public static void main(String[] args) {
Long v = null;
Long a = new Random().nextBoolean() ? v : 0L;
}
通过javap -c
命令查看main
方法:
0 aconst_null
1 astore_1
2 new #68
5 dup
6 invokespecial #69 >
9 invokevirtual #70
12 ifeq 22 (+10)
15 aload_1
16 invokevirtual #71
19 goto 23 (+4)
22 lconst_0
23 invokestatic #66
26 astore_2
27 return
可以看出,冒号表达式其实变成了:
Long a = Long.valueOf(new Random().nextBoolean() ? v.longValue() : 0L);
这样的话,如果 Random 随机的是 true,则会抛出NullPointerException
有时候,我们需要null
转换成默认值,一般像common-lang
的ObjectUtils
里面的defaultIfNull
这么写:
Long a = 6L;
a = a != null ? a : 0L;
这样,虽然没错,但是会多出一步自动拆箱,再封箱,查看字节码:
0 ldc2_w #68 <6>
3 invokestatic #66
6 astore_1
7 aload_1
8 ifnull 18 (+10)
11 aload_1
12 invokevirtual #70
15 goto 19 (+4)
18 lconst_0
19 invokestatic #66
22 astore_1
23 return
相当于:
a = Long.valueOf(a != null ? a.longValue() : 0L);
这样的话,无论 a 是不是 null,都会多出来这些拆箱封箱,效率不好。
对于这种场景,考虑到效率,还是老老实实,写 if-else 不要用三目运算符了。