JDK核心JAVA源码解析(8)

Wesley13
• 阅读 598

想写这个系列很久了,对自己也是个总结与提高。原来在学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 <java/lang/Long.valueOf>
 6 astore_1
 7 aload_1
 8 invokevirtual #70 <java/lang/Long.longValue>
11 lstore_2
12 lload_2
13 aload_1
14 invokevirtual #70 <java/lang/Long.longValue>
17 lcmp
18 ifle 21 (+3)
21 return

可以看到,调用Long.valueOf自动封箱,调用Long.longValue自动拆箱。

自动装箱拆箱时机

1.赋值还有比较运算时,类型不一致,会自动装箱拆箱

public static void main(String[] args) {
       Long v = 6L;
       long a = v;
}

通过javap -c命令查看:

 0 ldc2_w #68 <6>
 3 invokestatic #66 <java/lang/Long.valueOf>
 6 astore_1
 7 aload_1
 8 invokevirtual #70 <java/lang/Long.longValue>
11 lstore_2
12 return

可以看到,调用Long.valueOf自动封箱,调用Long.longValue自动拆箱。

2.方法调用时,类型不一致,会自动装箱拆箱

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 <java/lang/Long.valueOf>
 6 astore_1
 7 aload_1
 8 invokevirtual #70 <java/lang/Long.longValue>
11 invokestatic #71 <com/hopegaming/order/revo/controller/backend/CustomerAnalysisController.test1>
14 aload_1
15 invokevirtual #70 <java/lang/Long.longValue>
18 lstore_2
19 lload_2
20 invokestatic #66 <java/lang/Long.valueOf>
23 invokestatic #72 <com/hopegaming/order/revo/controller/backend/CustomerAnalysisController.test2>
26 return

调用方法前,发生了自动拆箱与自动装箱。

3. 对于同时有封装类型和原始类型两种参数的重载,不会发生自动封箱拆箱

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 <java/lang/Long.valueOf>
 6 astore_1
 7 aload_1
 8 invokestatic #70 <com/hopegaming/order/revo/controller/backend/CustomerAnalysisController.test1>
11 aload_1
12 invokevirtual #71 <java/lang/Long.longValue>
15 lstore_2
16 lload_2
17 invokestatic #72 <com/hopegaming/order/revo/controller/backend/CustomerAnalysisController.test1>
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 <java/util/Random>
 5 dup
 6 invokespecial #69 <java/util/Random.<init>>
 9 invokevirtual #70 <java/util/Random.nextBoolean>
12 ifeq 22 (+10)
15 aload_1
16 invokevirtual #71 <java/lang/Long.longValue>
19 goto 23 (+4)
22 lconst_0
23 invokestatic #66 <java/lang/Long.valueOf>
26 astore_2
27 return

可以看出,冒号表达式其实变成了:

Long a = Long.valueOf(new Random().nextBoolean() ? v.longValue() : 0L);

这样的话,如果 Random 随机的是 true,则会抛出NullPointerException

三目运算符判断null返回默认值效率问题

有时候,我们需要null转换成默认值,一般像common-langObjectUtils里面的defaultIfNull这么写:

Long a = 6L;
a = a != null ? a : 0L;

这样,虽然没错,但是会多出一步自动拆箱,再封箱,查看字节码:

 0 ldc2_w #68 <6>
 3 invokestatic #66 <java/lang/Long.valueOf>
 6 astore_1
 7 aload_1
 8 ifnull 18 (+10)
11 aload_1
12 invokevirtual #70 <java/lang/Long.longValue>
15 goto 19 (+4)
18 lconst_0
19 invokestatic #66 <java/lang/Long.valueOf>
22 astore_1
23 return

相当于:

a = Long.valueOf(a != null ? a.longValue() : 0L);

这样的话,无论 a 是不是 null,都会多出来这些拆箱封箱,效率不好。

对于这种场景,考虑到效率,还是老老实实,写 if-else 不要用三目运算符了。

点赞
收藏
评论区
推荐文章
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java架构师之路:推荐的15本书
一对于没有Java编程经验的程序员要入门,随便读什么入门书籍都一样,这个阶段需要你快速的掌握Java基础语法和基本用法,宗旨就是“囫囵吞枣不求甚解”,先对Java熟悉起来再说。用很短的时间快速过一遍Java语法,连懵带猜多写写代码,要“知其然”。在有了一定的Java编程经验之后,你需要“知其所以然”了。这个时候《Java编程
3A网络 3A网络
2年前
一文了解 Java 中的构造器
一文了解Java中的构造器C引入了构造器(constructor,也叫构造函数)的概念,它是在创建对象时被自动调用的特殊方法。Java也采用了构造器,并且还提供了一个垃圾收集器(garbagecollector),当不再使用内存资源的时候,垃圾收集器会自动将其释放。构造器定义在Java中,可以通过编写构造器来确保每个对象的初始化。但是这
Stella981 Stella981
3年前
Integer比较相等
Integertype1;??? 我们知道Integer是int的包装类,在jdk1.5以上,可以实现自动装箱拆箱,就是jdk里面会自动帮我们转换,不需要我们手动去强转,所以我们经常在这两种类型中随意写,平时也没什么注意但Integer他是对象,我们知道比较的是堆中的地址,但有个奇怪的事是,如果Integera123,
Wesley13 Wesley13
3年前
JDK核心JAVA源码解析(1)
想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。首先我们从所有类的父类Object开始:1\.Object类(1)hashCode方法和equals方法
Wesley13 Wesley13
3年前
Java之基本数据类型
本文章分为四个部分:1、基本数据类型的介绍2、类型转换3、装箱和拆箱4、有道练习5、增:编码的那点事儿_\基本数据类型的介绍_Java有8种基本数据类型,其中有
Wesley13 Wesley13
3年前
JDK核心JAVA源码解析(4)
想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。本篇文章针对堆外内存与DirectBuffer进行深入分析,了解Java对于堆外内存处理的机制,为下一篇文件IO做好准备Java堆栈内存与堆外内
Wesley13 Wesley13
3年前
go基础语法
1.常量定义用const关键字修饰常量名并赋值,常量命名不同于java等语言,golang中一般用小写,因为在golang中首字母大写表示public权限consta32.常量使用使用数值常量进行运算时不需要进行强制类型转换,编译器会自动识别consta,b3,4va
Wesley13 Wesley13
3年前
JDK核心JAVA源码解析(7)
想写这个系列很久了,对自己也是个总结与提高。原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么。知其所以然方能印象深刻并学以致用。本篇文章针对JAVA中集合类LinkedList进行分析,通过代码解释Java中的Failfast设计思想,以及LinkedList底层实现和与A
Wesley13 Wesley13
3年前
Java的自动装箱与拆箱(Autoboxing and unboxing)
一、什么是自动装箱拆箱 很简单,下面两句代码就可以看到装箱和拆箱过程1//自动装箱2Integertotal99;34//自动拆箱5inttotalprimtotal;简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。