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