Java的自动装箱/拆箱
概述
自JDK1.5开始, 引入了自动装箱/拆箱这一语法糖, 它使程序员的代码变得更加简洁, 不再需要进行显式转换。基本类型与包装类型在某些操作符的作用下, 包装类型调用valueOf()方法将原始类型值转换成对应的包装类对象的过程, 称之为自动装箱; 反之调用xxxValue()方法将包装类对象转换成原始类型值的过程, 则称之为自动拆箱。
实现原理
首先我们用javap -c AutoBoxingDemo命令将下面代码反编译:
public class AutoBoxingDemo {
public static void main(String[] args) {
Integer m = 1;
int n = m;
}
}
反编译后结果:
从反编译后的字节码指令中可以看出, Integer m = 1; 其实底层就是调用了包装类Integer的valueOf()方法进行自动装箱, 而 int n = m; 则是底层调用了包装类的intValue()方法进行自动拆箱。
其中Byte、Short、Integer、Long、Boolean、Character这六种包装类型在进行自动装箱时都使用了缓存策略, 下面是Integer类的缓存实现机制:
/**
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*/
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
} private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[]; static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h; cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
} private IntegerCache() {}
}
从Integer的源代码我们能得知, 当进行自动装箱的数值在[-128, 127]之间时, 调用valueOf()方法返回的是Integer缓存中已存在的对象引用。否则每次都是new一个新的包装类实例。
而Double、Float这两种包装类型因为是浮点数, 不像整数那样在某个范围内的数值个数是有限的, 所以它们没有使用缓存实现机制, 下面是Double包装类的自动装箱的源代码:
public static Double valueOf(double d) {
return new Double(d);
}
举例说明
public class AutoBoxingDemo {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
Double i = 1.0;
Double j = 1.0;
Boolean k = true;
Boolean l = true;
//数值在[-128, 127]范围内,自动装箱时都是从缓存中获取对象引用,所以结果为true
System.out.println(c==d);
//数值在[-128, 127]范围外,自动装箱时每次都是new新的对象,所以结果为false
System.out.println(e==f);
//当"=="运算符的两个操作数都是包装器类型的引用,则比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程), 所以结果为true
System.out.println(c==(a+b));
//对于包装类型,当equals()方法比较的是同一类型时(比如Integer与Integer比较),实际比较的是他们的数值是否相等。如比较的不是同一类型,则不会进行类型转换,直接返回false。所以结果为true
System.out.println(c.equals(a+b));
//因为有算术运算,自动拆箱后再比较数值,所以结果为true
System.out.println(g==(a+b));
//因为equals()方法比较的是不同包装类型,不会进行类型转换,所以结果为false
System.out.println(g.equals(a+b));
//因为a+h先触发自动拆箱,a转为int类型后,需要隐式向上提升类型为long后再进行运算,最后再自动装箱转为Long包装类型,且两边数值相等,所以结果为true
System.out.println(g.equals(a+h));
//Double类没有缓存,每次都是new一个新的实例,所以结果为false
System.out.println(i == j);
//Boolean自动装箱,指向的都是同一个实例,所以结果为true
System.out.println(k == l);
}
}
在上面示例中, 关于结果的解析已经阐述的很清楚了, 主要有两个地方具有迷惑性。当"=="运算符的两个操作数都是包装器类型的引用,则比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会先触发自动拆箱的过程)。
对于包装类型,当equals()方法比较的是同一类型时(比如Integer与Integer比较), 实际比较的是他们的数值是否相等; 如比较的不是同一类型(比如Integer与Long比较), 则不会进行类型转换,直接返回false。下面是Integer类的equals()方法的源代码:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
另外我们也可以反编译以上代码, 穿透语法糖的糖衣能帮助我们更容易了解这些具有迷惑性现象的背后原理:
自动装箱/拆箱带来的问题
自动拆箱下算术运算引起的空指针问题
private Double distinct;
private void setParam(Double dSrc, boolean flag) {
this.distinct = (flag) ? dSrc : 0d;
}
上面这段代码乍一看是没问题的, 但实际当dSrc为null时, 调用该方法会抛出空指针异常, 我们对其进行反编译:
可以看出, 当对包装类进行诸如三目运算符的算术运算时, 当数据类型不一致时, 编译器会自动拆箱转换为基本类型再进行运算, 所以当dSrc传入null值时, 调用doubleValue()方法拆箱就会报NP空指针异常。
这里我们可以在进行算术运算时, 统一数据类型, 避免编译器进行自动拆箱, 来解决拆箱下三目运算符的空指针问题。还是上面这个栗子, 我们将 this.distinct = (flag) ? dSrc : 0d; 修改成 this.distinct = (flag) ? dSrc : Double.valueOf(0); 即可解决, 重新反编译后如下, 因为类型一致, 没有再进行自动拆箱:
自动装箱的弊端
Integer sum = 0;
for(int i=1000; i<10000; i++){
sum+=i;
}
如上代码, 当在循环中对包装类型进行算术运算 sum = sum + i; 时, 会先触发自动拆箱, 进行加法运算后, 再进行自动装箱, 且因为运算后的sum数值不在缓存范围之内, 所以每次都会new一个新的Integer实例。所以上面的循环结束后, 将会在内存中创建9000个无用的Integer实例对象, 这样会大大降低程序的性能, 增加GC的开销, 所以我们在写循环语句时一定要正确的声明变量类型, 避免因为自动装箱而引起不必要的性能问题。
重载与自动装箱
在JDK1.5之前, 没有引入自动装箱/拆箱这一语法糖, 当方法重载时, test(int num) 与 test(Integer num) 的形参没有任何关系。JDK1.5之后, 当调用重载的方法时, 编译器不会进行自动装箱操作, 我们可以通过运行下面的代码示例来演示。
public static void testAutoBoxing(int num) {
System.out.println("方法形参为原始类型");
} public static void testAutoBoxing(Integer num) {
System.out.println("方法形参为包装类型");
} public static void main(String[] args) {
int m = 2;
testAutoBoxing(m);
Integer n = m;
testAutoBoxing(n);
}
运行结果如下:
很明显, 当调用重载的方法时, 编译器不会对传入的实参进行自动装箱操作。
参考资料
Autoboxing and Unboxing (The Java Tutorials > Lea...
作者:张小凡
出处:https://www.cnblogs.com/qingshanli/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。
Java的自动装箱/拆箱的更多相关文章
- Java 的自动装箱拆箱
Java 是面向对象的语言,其基本数据类型也就有了相对应的类,称为包装类.以下是基本数据类型对应的包装类: 基本数据类型 包装类 byte(1字节) Byte short(2字节) Short int ...
- JAVA的自动装箱拆箱
转自:http://www.cnblogs.com/danne823/archive/2011/04/22/2025332.html 蛋呢 的空间 ??什么是自动装箱拆箱 基本数据类型的自动装箱(a ...
- 通过源码了解Java的自动装箱拆箱
什么叫装箱 & 拆箱? 将int基本类型转换为Integer包装类型的过程叫做装箱,反之叫拆箱. 首先看一段代码 public static void main(String[] args) ...
- java自动装箱拆箱总结
对于java1.5引入的自动装箱拆箱,之前只是知道一点点,最近在看一篇博客时发现自己对自动装箱拆箱这个特性了解的太少了,所以今天研究了下这个特性.以下是结合测试代码进行的总结. 测试代码: int a ...
- Java八种基本数据类型的大小,以及封装类,自动装箱/拆箱的用法?
参考:http://blog.csdn.net/mazhimazh/article/details/16799925 1. Java八种基本数据类型的大小,以及封装类,自动装箱/拆箱的用法? 原始类型 ...
- JAVA自动装箱拆箱与常量池
java 自动装箱与拆箱 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的 ...
- java基础1.5版后新特性 自动装箱拆箱 Date SimpleDateFormat Calendar.getInstance()获得一个日历对象 抽象不要生成对象 get set add System.arrayCopy()用于集合等的扩容
8种基本数据类型的8种包装类 byte Byte short Short int Integer long Long float Float double Double char Character ...
- Java中的自动装箱拆箱
Java中的自动装箱拆箱 一.自动装箱与自动拆箱 自动装箱就是将基本数据类型转换为包装类类型,自动拆箱就是将包装类类型转换为基本数据类型. 1 // 自动装箱 2 Integer total = 90 ...
- Java 自动装箱/拆箱
自动装箱/拆箱大大方便了基本类型(8个基本类型)数据和它们包装类的使用 自动装箱 : 基本类型自动转为包装类(int >> Integer) 自动拆箱: 包装类自动转为基本类型(Integ ...
随机推荐
- Electron为文件浏览器创建图标(三)
在前面的文章中,请看之前文章,我们已经完成了使用 electron做文件浏览器这么一个应用,现在我们需要为应用创建图标操作.为应用创建图标以后,我们就可以从计算机中与其他应用区分开来,如果我们自己会做 ...
- Mint-ui全局引入
1.Mint-ui在全局引入之后,在组件中使用其中的js函数,如toast() this.$toast('在全局引入之后可以直接在this.$toast中使用')
- react 项目全家桶构件流程
资源:create-react-app.react.react-dom.redux.react-redux.redux-thunk.react-router-dom.antd-mobile/antd. ...
- 利用jsonconvert来转换json数据格式 (对象转为json)
今天学了一下.net的WCF组件,边心血来潮,想着现在不都是前后分离,调接口开发不,于是赶紧写了一简单的后台数据,哈哈 废话不多说,直接上代码: 注意需要导入库! 实体类:Customer us ...
- 开源SQL审核平台——Archery 安装、部署心得
目录 0.软件版本及项目地址 1.安装python venv环境 1.1.安装 python36 1.2.创建 Python venv 环境(Python>=3.6.5,建议使用虚拟环境 ) 1 ...
- kafka源码分析(二)Metadata的数据结构与读取、更新策略
一.基本思路 异步发送的基本思路就是:send的时候,KafkaProducer把消息放到本地的消息队列RecordAccumulator,然后一个后台线程Sender不断循环,把消息发给Kafka集 ...
- 《Python 3网络爬虫开发实战中文》超清PDF+源代码+书籍软件包
<Python 3网络爬虫开发实战中文>PDF+源代码+书籍软件包 下载: 链接:https://pan.baidu.com/s/18yqCr7i9x_vTazuMPzL23Q 提取码:i ...
- c++学习书籍推荐《数据结构C++语言描述:应用标准模板库STL(第2版)》下载
本书是Ford和Topp两位教授于1996看出版的名著Data Structures with C++的第2版,在全球范围内已经有数以万计的学生从中受益.作者将C++语言作为算法描述语言,应用包含规范 ...
- c++学习书籍推荐《C++ Templates》下载
详细讲解C++模板语言的概念. 使用C++模板的常用设计技巧. 应用例证(其中一些是“高 级”应用). 百度云及其他网盘下载地址:点我 名人推荐 如果今年你只打算买一本C++的书,那就选<C++ ...
- C++学习书籍推荐《C++编程思想第二版第一卷》下载
百度云及其他网盘下载地址:点我 编辑推荐 “经典原版书库”是响应教育部提出的使用原版国外教材的号召,为国内高校的计算机教学度身订造的.<C++编程思想>(英文版第2版)是书库中的一本,在广 ...