JDK核心JAVA源码解析(8) - 自动封箱拆箱与效率的思考

简介: JDK核心JAVA源码解析(8) - 自动封箱拆箱与效率的思考

想写这个系列很久了,对自己也是个总结与提高。原来在学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 不要用三目运算符了。

相关文章
|
1天前
|
Java
解析java中的数组
解析java中的数组
10 3
|
1天前
|
Java
JDK环境下利用记事本对java文件进行运行编译
JDK环境下利用记事本对java文件进行运行编译
8 0
|
2天前
|
存储 Java 程序员
Java面向对象编程的基础概念解析
Java面向对象编程的基础概念解析
12 0
|
2天前
|
Java 开发工具
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
|
3天前
|
Java 编译器 对象存储
java一分钟之Java入门:认识JDK与JVM
【5月更文挑战第7天】本文介绍了Java编程的基础——JDK和JVM。JDK是包含编译器、运行时环境、类库等的开发工具包,而JVM是Java平台的核心,负责执行字节码并实现跨平台运行。常见问题包括版本不匹配、环境变量配置错误、内存溢出和线程死锁。解决办法包括选择合适JDK版本、正确配置环境变量、调整JVM内存参数和避免线程死锁。通过代码示例展示了JVM内存管理和基本Java程序结构,帮助初学者更好地理解JDK和JVM在Java编程中的作用。
15 0
|
3天前
|
分布式计算 Java API
Java8 Lambda实现源码解析
Java8的lambda应该大家都比较熟悉了,本文主要从源码层面探讨一下lambda的设计和实现。
|
4天前
|
缓存 自然语言处理 JavaScript
万字长文深度解析JDK序列化原理及Fury高度兼容的极致性能实现
Fury是一个基于JIT动态编译的高性能多语言原生序列化框架,支持Java/Python/Golang/C++/JavaScript等语言,提供全自动的对象多语言/跨语言序列化能力,以及相比于别的框架最高20~200倍的性能。
|
2天前
|
Java Shell 开发者
都2024年了!你还不知道在Docker中安装jdk?
都2024年了!你还不知道在Docker中安装jdk?
|
2天前
|
存储 Cloud Native Java
Anolis安装Jdk保姆级教学
Anolis安装Jdk保姆级教学
|
6天前
|
Oracle Java 关系型数据库
windows 下 win11 JDK17安装与环境变量的配置(配置简单详细,包含IJ中java文件如何使用命令运行)
本文介绍了Windows 11中安装JDK 17的步骤,包括从官方网站下载JDK、配置环境变量以及验证安装是否成功。首先,下载JDK 17的安装文件,如果没有Oracle账户,可以直接解压缩文件到指定目录。接着,配置系统环境变量,新建`JAVA_HOME`变量指向JDK安装路径,并在`Path`变量中添加。然后,通过命令行(cmd)验证安装,分别输入`java -version`和`javac -version`检查版本信息。最后,作者分享了如何在任意位置运行Java代码,包括在IntelliJ IDEA(IJ)中创建的Java文件,只需去掉包声明,就可以通过命令行直接运行。

推荐镜像

更多