惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
C
Cisco Blogs
L
Lohrmann on Cybersecurity
宝玉的分享
宝玉的分享
I
Intezer
aimingoo的专栏
aimingoo的专栏
Cisco Talos Blog
Cisco Talos Blog
The Register - Security
The Register - Security
GbyAI
GbyAI
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
Cyberwarzone
Cyberwarzone
爱范儿
爱范儿
I
InfoQ
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
P
Palo Alto Networks Blog
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
腾讯CDC
阮一峰的网络日志
阮一峰的网络日志
NISL@THU
NISL@THU
T
Threatpost
T
The Blog of Author Tim Ferriss
云风的 BLOG
云风的 BLOG
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Schneier on Security
Security Latest
Security Latest
Martin Fowler
Martin Fowler
H
Help Net Security
小众软件
小众软件
The Hacker News
The Hacker News
Know Your Adversary
Know Your Adversary
博客园 - 司徒正美
人人都是产品经理
人人都是产品经理
T
The Exploit Database - CXSecurity.com
Jina AI
Jina AI
Engineering at Meta
Engineering at Meta
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
IT之家
IT之家
WordPress大学
WordPress大学
S
Securelist

Java技术经验分享

生产故障处理SOP分享 | Java技术经验分享 系统稳定性建设实践总结【转载】 | Java技术经验分享 valine访问leancloud国际版异常,评论失效修复 | Java技术经验分享 匆匆忙忙的2021 | Java技术经验分享 聊一下换工作 | Java技术经验分享 IoT系列(2):WIFI设备常见配网方案介绍 | Java技术经验分享 IoT系列(1):什么是物联网 | Java技术经验分享 Java8特性2 - StreamApi | Java技术经验分享 Java8特性1 - lambda表达式&函数式接口 | Java技术经验分享 设计模式(5)-适配器模式 | Java技术经验分享 设计模式(4)-建造者模式 | Java技术经验分享 设计模式(3)-原型模式与浅拷贝和深拷贝 | Java技术经验分享 设计模式(2)-工厂模式图文介绍 | Java技术经验分享 设计模式(1)-带你了解3类8种单例模式 | Java技术经验分享 Java时间处理5---Java8中时区相关类库介绍 | Java技术经验分享 Java时间处理4---Java8中LocalDate、LocalTime、LocalDateTime介绍 | Java技术经验分享 Java时间处理3---Java8中Instant、Duration、Period、Clock介绍 | Java技术经验分享 一些有意思的问答 | Java技术经验分享 Nacos系列博客说明 | Java技术经验分享 菜鸡程序员的2019年度总结 | Java技术经验分享 Java中“附近的人”实现方案讨论及代码实现 | Java技术经验分享 Java时间处理2----时区TimeZone类方法探究(Java8以前) | Java技术经验分享 Java时间处理1----Date和Calendar方法探究(Java8以前) | Java技术经验分享 FastJson中JSONString、JavaBean、JSONObject、JSONArray的转换关系及API示例 | Java技术经验分享 2019.11软考软件设计师归来心得体会及复习备考指南 | Java技术经验分享 你还没用过“约定式提交”吗?那你赶紧来补补知识吧 | Java技术经验分享 教你如何看懂UML中的类图及类图中的关系 | Java技术经验分享 设计模式总览 | Java技术经验分享 萌新入门Github请看这里,学不会远程教 | Java技术经验分享 Hexo的工作原理探究 | Java技术经验分享 Hexo-theme-butterfly修改调整记录教程 | Java技术经验分享 排序8:基数排序 | Java技术经验分享 排序7:归并排序 | Java技术经验分享 排序6:快速排序 | Java技术经验分享 排序5:冒泡排序 | Java技术经验分享 排序4:堆排序 | Java技术经验分享 排序3:选择排序 | Java技术经验分享 排序2:希尔排序 | Java技术经验分享 排序1:直接插入排序 | Java技术经验分享 推荐一款博客一文多发的良心工具OpenWrite | Java技术经验分享 近期学习计划 | Java技术经验分享 Nacos(九):Nacos集群部署和遇到的问题 | Java技术经验分享 Nacos(八):Nacos持久化 | Java技术经验分享 Nacos(七):Nacos共享配置 | Java技术经验分享 Nacos(六):多环境下如何“管理”及“隔离”配置和服务 | Java技术经验分享 Nacos(五):多环境下如何“读取”Nacos中相应的配置 | Java技术经验分享 Nacos(四):SpringCloud项目中接入Nacos作为配置中心 | Java技术经验分享 Nacos(三):Nacos与OpenFeign的对接使用 | Java技术经验分享 Nacos(二):SpringCloud项目中接入Nacos作为注册中心 | Java技术经验分享 Nacos(一):Nacos介绍 | Java技术经验分享 20190719小组分享 | Java技术经验分享 Java中equals和HashCode方法的分析 | Java技术经验分享 Java中==和equals方法的分析 | Java技术经验分享 About-blog | Java技术经验分享 Java中的编译、反编译和反编译工具全家桶分享 | Java技术经验分享 finalize()的生命周期(执行过程) | Java技术经验分享 Java关键字之final、finally与finalize方法 | Java技术经验分享 Java中重写、重载 | Java技术经验分享 Java中面向对象的三大特征:继承、封装、多态 | Java技术经验分享 DockerFile介绍 | Java技术经验分享 Docker环境下安装Gitlab | Java技术经验分享 Docker中私有仓库的搭建流程 | Java技术经验分享 Centos7下两种方式安装Docker-CE | Java技术经验分享 Vert.x创建一个Http服务 | Java技术经验分享 Vert.x创建TCP服务端及客户端 | Java技术经验分享 Vert.x Core(二)- Event Bus(事件总线) | Java技术经验分享 Vert.x-Core(一)- 基础篇 | Java技术经验分享 SpringBoot项目中实现国际化 | Java技术经验分享 Vert.x介绍 | Java技术经验分享 毕设选题项目本地运行环境搭建教程 | Java技术经验分享 Jupyter Notebooks的安装和使用介绍 | Java技术经验分享 算法笔试题:1元,5元,10元,20元,50元、100元面值人民币组合给定x元的问题 | Java技术经验分享 Quartz学习总结 | Java技术经验分享 SpringBoot2.x集成Redis | Java技术经验分享 SpringBoot2.x集成MongoDB | Java技术经验分享 [SpringCloud学习] - 浅谈微服务架构 | Java技术经验分享 基于hexo和coding免费搭建个人博客网站 | Java技术经验分享 Hello World | Java技术经验分享
Java中的自动拆装箱、装箱缓存 | Java技术经验分享
文章作者: LarsCheng · 2019-06-17 · via Java技术经验分享

前言

Java 是一种面向对象的编程语言,Java 中的类把方法与数据类型连接在一起,构成了自包含式的处理单元。但在 Java 中不能定义基本类型对象,为了能将基本类型视为对象处理,并能连接相关方法,Java 为每个基本数据类型都提供了包装类,如 int 型数值的包装类 Integer,boolean 型数值的包装类 Boolean 等。这样便可以把这些基本类型转换为对象来处理了。

在Java中包含了8种基本数据类型,与之相对应的还有8种包装类,他们之间的对应关系如下:

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

什么是自动拆装箱

Java中不能定义基本数据类型的对象,因此我们可以使用包装类,每种基本数据类型都有自己对应的包装类。
基本数据类型与包装类之间的转换过程就涉及到了自动拆装箱。

  • 基本数据类型转换为包装类的过程称作自动装箱
  • 包装类转换为基本数据类型的过程称作自动拆箱

自动拆装箱的实现原理

举一个栗子:

1
2
3
4
5
6
7
8
9
public class AutoBoxing {
public static void main(String[] args) {
int i = 10;

Integer ii = i;

int iii = ii;
}
}

上面的代码实际上就是Java中的语法糖,通过对.class文件进行反编译之后就可以看到代码的真面目:

1
2
3
4
5
6
7
public class AutoBoxing {
public static void main(String[] arrstring) {
int n = 10;
Integer n2 = Integer.valueOf(n);
int n3 = n2.intValue();
}
}

从反编译后的代码可以看到,int类型到Integer的装箱过程是通过Integer.valueOf()实现,Integer到int的拆箱过程是通过intValue()实现。

刚好我们测试下其他七种数据类型的拆装箱过程是怎么样的,代码如下AutoBox.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class AutoBox {

public static void main(String[] args) {
Integer aa = 10;
int aaa = aa;

Byte bb = 20;
byte bbb = bb;

Short cc = 30;
short ccc = cc;

Long d = 40L;
long dd = d;

Float e = 50f;
float ee = e;

Double f = 60d;
double ff = f;

Character g = 'a';
char gg = g;

Boolean h = true;
boolean hh = h;

}
}

直接对AutoBox.java文件进行编译后,对AutoBox.class文件反编译分析,命令如下

//编译
javac AutoBox.java
//反编译分析
javap -c AutoBox.class

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Compiled from "AutoBox.java"
public class com.zhengql.practice.autoBox.AutoBox {
public com.zhengql.practice.autoBox.AutoBox();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: invokevirtual #3 // Method java/lang/Integer.intValue:()I
10: istore_2
11: bipush 20
13: invokestatic #4 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
16: astore_3
17: aload_3
18: invokevirtual #5 // Method java/lang/Byte.byteValue:()B
21: istore 4
23: bipush 30
25: invokestatic #6 // Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
28: astore 5
30: aload 5
32: invokevirtual #7 // Method java/lang/Short.shortValue:()S
35: istore 6
37: ldc2_w #8 // long 40l
40: invokestatic #10 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
43: astore 7
45: aload 7
47: invokevirtual #11 // Method java/lang/Long.longValue:()J
50: lstore 8
52: ldc #12 // float 50.0f
54: invokestatic #13 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
57: astore 10
59: aload 10
61: invokevirtual #14 // Method java/lang/Float.floatValue:()F
64: fstore 11
66: ldc2_w #15 // double 60.0d
69: invokestatic #17 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
72: astore 12
74: aload 12
76: invokevirtual #18 // Method java/lang/Double.doubleValue:()D
79: dstore 13
81: bipush 97
83: invokestatic #19 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
86: astore 15
88: aload 15
90: invokevirtual #20 // Method java/lang/Character.charValue:()C
93: istore 16
95: iconst_1
96: invokestatic #21 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
99: astore 17
101: aload 17
103: invokevirtual #22 // Method java/lang/Boolean.booleanValue:()Z
106: istore 18
108: return
}
经过测试,其他7种基本数据类型到包装类的装箱拆箱原理都与int/Integer相同,
自动装箱都是通过包装类的valueOf()方法来实现的,
自动拆箱都是通过包装类对象的xxxValue()来实现的

什么时候用到自动拆装箱

  1. 赋值操作时
1
Integer a = 1;
  1. 包装类之间运算时(±*/)
1
2
3
Integer a = 1;
Integer b = 2;
int c = a + b;
  1. 比较运算时
1
2
Integer a=1;
boolean b = a==1;
  1. 向集合中添加基本数据类型时
1
2
3
4
List<Integer> list = new ArrayList<>();
for (int i = 1; i < 10; i ++){
list.add(i);
}
  1. 方法调用、参数返回时
1
2
3
4
5
6
7
8
9
10
11
public class AutoBox {

private static int test(int i){
return i + 1;
}
public static void main(String[] args) {
Integer i = 1;
int a = test(i);
Integer b = test(1);
}
}

装箱缓存

其实,在自动装箱过程中还存在一种缓存的操作,且看下面一道题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AutoBoxTest {
public static void main(String[] args) {
Integer a = 30;
Integer b = 30;

if (a==b){
System.out.println("a、b:内存地址相同");
}else {
System.out.println("a、b:不同的两个对象");
}

Integer c = 300;
Integer d = 300;

if (c==d){
System.out.println("c、d:内存地址相同");
}else {
System.out.println("c、d:不同的两个对象");
}
}
}

这道题乍一看是不是觉得匪夷所思,怎么会有这种沙雕题目,两个对象类型用等号判断大小,很明显都是new出来的对象,肯定指向不同的内存地址啊,肯定不相等了。然鹅运行的结果如下:

1
2
a、b:内存地址相同
c、d:不同的两个对象

可以看到为什么同样的操作,c和d就符合判断逻辑,而a和b就偏偏指向同一个对象呢?

这是因为在自动装箱过程中,Integer对象通过使用相同的对象引用实现对象的缓存和重用。

那么问题又来了,既然有缓存操作,那为什么a、b有,c、d却没有呢?

来看一下Integer自动装箱的源码:

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

首先判断入参i是否处于[IntegerCache.low,IntegerCache.high]区间内,如果i值在区间内,则从缓存IntegerCache.cache中读取某一个值返回,反之直接new一个Integer对象,这说明触发缓存操作是根据i值的范围决定的

那这个范围又是多少呢?阅读该方法的注释:

1
2
This method will always cache values in the range -128 to 127,
inclusive, and may cache other values outside of this range.

此方法默认缓存[-128,127]范围内的值,但也可以缓存范围外的其他值,这里是因为区间右侧的IntegerCache.high是可配置的。

看到这里,终于明白,最开始的那道题目,为什么ab和cd的结果会完全不一样,是因为a、b的值在[-128,127]区间内,而c、d的值不在此范围内。

那么,既然Integer有缓存这个骚操作,那其他的包装类是不是也有呢?直接去看每个包装类的valueOf方法就可以知道了。

这里我就不贴源码了,查看后的结论是,其他的7种包装类中,所有的整数类型的类,在自动装箱时都有类似于Integer的这种缓存操作,只不过他们各自的触发情况不同,结果整理如下:

包装类 缓存机制 触发条件 备注
Byte ByteCache [-128,127]
Short ShortCache [-128,127]
Integer IntegerCache [-128,127] 最大值可配置
Long LongCache [-128,127]
Float - -
Double - -
Boolean - -
Character CharacterCache [0,127]

总结

自动装箱和拆箱方便了我们开发人员,但是在使用自动拆装箱时也有很多翻车现场,最容易出现的就是空指针,所以在使用自动拆装箱时一定要防止空指针。

自动装箱过程中涉及到对象的创建等操作,如果在循环体中大量的拆装箱操作,势必会浪费资源,所以何时使用合理的使用自动拆装箱是尤为重要。

参考和感谢

Java中整型的缓存机制:https://www.hollischuang.com/archives/1174