概述

自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...

深入剖析Java中的装箱和拆箱 - 海 子 - 博客园

Java 自动装箱与拆箱的实现原理 - 简书

Java自动拆箱下, 三目运算符的潜规则

Java中的自动装箱与拆箱

作者:张小凡
出处:https://www.cnblogs.com/qingshanli/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。

Java的自动装箱/拆箱的更多相关文章

  1. Java 的自动装箱拆箱

    Java 是面向对象的语言,其基本数据类型也就有了相对应的类,称为包装类.以下是基本数据类型对应的包装类: 基本数据类型 包装类 byte(1字节) Byte short(2字节) Short int ...

  2. JAVA的自动装箱拆箱

    转自:http://www.cnblogs.com/danne823/archive/2011/04/22/2025332.html 蛋呢  的空间 ??什么是自动装箱拆箱 基本数据类型的自动装箱(a ...

  3. 通过源码了解Java的自动装箱拆箱

    什么叫装箱 & 拆箱? 将int基本类型转换为Integer包装类型的过程叫做装箱,反之叫拆箱. 首先看一段代码 public static void main(String[] args) ...

  4. java自动装箱拆箱总结

    对于java1.5引入的自动装箱拆箱,之前只是知道一点点,最近在看一篇博客时发现自己对自动装箱拆箱这个特性了解的太少了,所以今天研究了下这个特性.以下是结合测试代码进行的总结. 测试代码: int a ...

  5. Java八种基本数据类型的大小,以及封装类,自动装箱/拆箱的用法?

    参考:http://blog.csdn.net/mazhimazh/article/details/16799925 1. Java八种基本数据类型的大小,以及封装类,自动装箱/拆箱的用法? 原始类型 ...

  6. JAVA自动装箱拆箱与常量池

    java 自动装箱与拆箱 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的 ...

  7. 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 ...

  8. Java中的自动装箱拆箱

    Java中的自动装箱拆箱 一.自动装箱与自动拆箱 自动装箱就是将基本数据类型转换为包装类类型,自动拆箱就是将包装类类型转换为基本数据类型. 1 // 自动装箱 2 Integer total = 90 ...

  9. Java 自动装箱/拆箱

    自动装箱/拆箱大大方便了基本类型(8个基本类型)数据和它们包装类的使用 自动装箱 : 基本类型自动转为包装类(int >> Integer) 自动拆箱: 包装类自动转为基本类型(Integ ...

随机推荐

  1. 【JVM】01虚拟机内存模型

    学习链接:https://blog.csdn.net/u010425776/article/details/51170118 博主整理的条理清晰,在这里先感谢博主分享 去年看视频学习写过一篇JVM的博 ...

  2. Axure中怎么制作锚点

    实现目标 点击导航条不同的菜单,页面跳到菜单相应的位置,实现页面自动滚动. 1.  拖入编辑区3个矩形,并相应命名为菜单1,菜单2 ,菜单3: 2.  再新建3个想要制作的文本框准备进行锚点滚动: 3 ...

  3. python读取excel文件中所有sheet表格

    sales: store: """(1)用load_workbook函数打开excel文件,返回一个工作簿对象 (2)用工作簿对象获取所有的sheet (3)第一个for ...

  4. JS处理时间戳、前台拿到日期时间戳,时间戳转日期格式

    今晚做分页的时候,遇到后台往前台传日期类型,会出现这种情况,好像是微软为了解决操作系统兼容性问题,将日期全部转换为从1970年1月1日至现在时间的时间戳.为了解决这个问题,特意百度了一番,整理了处理日 ...

  5. Python基础-使用range创建数字列表以及简单的统计计算和列表解析

    1.使用函数 range() numbers = list(range[1,6]) print (numbers) 结果: [1,2,3,4,5] 使用range函数,还可以指定步长,例如,打印1~1 ...

  6. flutter 如何实现文件读写(使用篇)

    flutter文件读写可以对磁盘文件进行操作,实现某些业务场景,那么我们开始来讲下这个文件读写操作. 使用的库插件(package) dart:io(用于数据处理) path_provider (用于 ...

  7. Codeforces Gym101246H:``North-East''(LIS+思维)

    http://codeforces.com/gym/101246/problem/H 题意:在二维平面上有n个点,从最左下角的点出发,每次走只能走在当前的点的右上角的点(xj > xi, yj ...

  8. vue项目缓存最佳实践

    需求 在开发vue的项目中有遇到了这样一个需求:一个视频列表页面,展示视频名称和是否收藏,点击进去某一项观看,可以收藏或者取消收藏,返回的时候需要记住列表页面的页码等状态,同时这条视频的收藏状态也需要 ...

  9. Scratch3 二次开发系列

       Scratch3.0来啦!!! Scratch做为图像化编程的首选语言,拖过积木块搭建实现动画游戏的制作.Scratch3添加了音乐.画笔.视频侦测.文字朗读.翻译等选择性下载扩展积木,可实现积 ...

  10. Windows OS添加USB3.0驱动!

    原因:现在的win7.win2008等一些早期的系统在安装时无法使USB鼠标和键盘... 1.  素材(dism64工具.UltraISO工具.U盘一个) 2.  先使用UltrISO工具制作一个wi ...