2019独角兽企业重金招聘Python工程师标准>>>

在本文中,笔者向大家介绍下Java中一个非常重要也非常有趣的特性,就是自动装箱与拆箱,并从源码中解读自动装箱与拆箱的原理,同时这种特性也留有一个陷阱。开发者如果不注意,就会很容易跌入这个陷阱。

自动装箱(Autoboxing)

定义

大家在平时编写Java程序时,都常常以以下方式来定义一个Integer对象:

[java]   view plain  copy
  1. Integer i=100;

从上面的代码中,大家可以得知,i为一个Integer类型的引用,100为Java中的基础数据类型(primitive data type)。而这种直接将一个基础数据类型传给其相应的封装类(wrapper class)的做法,便是自动装箱(Autoboxing)。

在jdk 1.5中,自动装箱首次被引入。而在jdk 1.5之前,如果你想要定义一个value为100的Integer对象,则需要这样做:

[java]   view plain  copy
  1. Integer i=new Integer (100);

原理

我们在以上代码“Integer i=100;”处打一个断点,跟踪一下。

接下来,我们可以看到,程序跳转到了Integer类的valueOf(int i)方法中

[java]   view plain  copy
  1. /**
  2. * Returns a <tt>Integer</tt> instance representing the specified
  3. * <tt>int</tt> value.
  4. * If a new <tt>Integer</tt> instance is not required, this method
  5. * should generally be used in preference to the constructor
  6. * {@link #Integer(int)}, as this method is likely to yield
  7. * significantly better space and time performance by caching
  8. * frequently requested values.
  9. *
  10. * @param  i an <code>int</code> value.
  11. * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
  12. * @since  1.5
  13. */
  14. public static Integer valueOf(int i) {
  15. if(i >= -128 && i <= IntegerCache.high)
  16. return IntegerCache.cache[i + 128];
  17. else
  18. return new Integer(i);
  19. }

换句话说,装箱就是jdk自己帮你完成了调用Integer.valueOf(100)。

拆箱(Unboxing)

定义

[java]   view plain  copy
  1. Integer integer100=100;
  2. int int100=integer100;

从上面的代码中,大家可看出integer100为一个Integer类型的引用,int100为一个int类型的原始数据类型。但是,我们可以将一个Integer类型的对象赋值给其相应原始数据类型的变量。这便是拆箱。

拆箱与装箱是相反的操作。装箱是将一个原始数据类型赋值给相应封装类的变量。而拆箱则是将一个封装类的变量赋值给相应原始数据类型的变量。装箱、拆箱的名字也取得相当贴切。

原理

笔者相信大家也都猜到了,拆箱过程中jdk为我们做了什么。我们还是通过实验来证明我们的猜想吧。

在以上代码的第二行代码打上断点,即在“int int100=integer100;”上打上断点,跟踪一下。

我们可以看到,程序跳转到了Integer的intValue()方法。

[java]   view plain  copy
  1. /**
  2. * Returns the value of this <code>Integer</code> as an
  3. * <code>int</code>.
  4. */
  5. public int intValue() {
  6. return value;
  7. }

也就是,jdk帮我们完成了对intValue()方法的调用。对于以上的实验而言,便是调用integer100的intValue()方法,将其返回值赋给了int100。

扩展

实验1

[java]   view plain  copy
  1. Integer integer400=400;
  2. int int400=400;
  3. System.out.println(integer400==int400);

在以上代码的第三行中,integer400与int400执行了==运行。而这两个是不同类型的变量,到底是integer400拆箱了,还是int400装箱了呢?运行结果是什么呢?

==运算是判断两个对象的地址是否相等或者判断两个基础数据类型的值是否相等。所以,大家很容易推测到,如果integer400拆箱了,则说明对比的是两个基础类型的值,那此时必然相等,运行结果为true;如果int400装箱了,则说明对比的是两个对象的地址是否相等,那此时地址必然不相等,运行结果为false。(至于为什么笔者对它们赋值为400,就是后面将要讲到的陷阱有关)。

我们实际的运行结果为true。所以是integer400拆箱了。对代码跟踪的结果也证明这一点。

实验2

[java]   view plain  copy
  1. Integer integer100=100;
  2. int int100=100;
  3. System.out.println(integer100.equals(int100));

在以上代码的第三行中,integer100的方法equals的参数为int100。我们知道equals方法的参数为Object,而不是基础数据类型,因而在这里必然是int100装箱了。对代码跟踪的结果也证明了这一点。

其实,如果一个方法中参数类型为原始数据类型,所传入的参数类型为其封装类,则会自动对其进行拆箱;相应地,如果一个方法中参数类型为封装类型,所传入的参数类型为其原始数据类型,则会自动对其进行装箱。

实验3

[java]   view plain  copy
  1. Integer integer100 = 100;
  2. int int100 = 100;
  3. Long long200 = 200l;
  4. System.out.println(integer100 + int100);
  5. System.out.println(long200 == (integer100 + int100));
  6. System.out.println(long200.equals(integer100 + int100));

在第一个实验中,我们已经得知,当一个基础数据类型与封装类进行==运算时,会将封装类进行拆箱。那如果+、-、*、/呢?我们在这个实验中,就可知道。

如果+运算,会将基础数据类型装箱,那么:

  • 第4行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,并执行这个对象的toString()方法,并输出”200”;
  • 第5行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,==运算将这个对象与long200对象进行对比,显然,将会输出false;
  • 第6行中,integer100+int100就会得到一个类型为Integer且value为200的对象o,Long的equals方法将long200与o对比,因为两都是不同类型的封装类,因而输出false;

如果+运算,会将封装类进行拆箱,那么:

  • 第4行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,再将b进行装箱得到o,执行这个对象的toString()方法,并输出”200”;
  • 第5行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b1,==运算将long200进行拆箱得到b2,显然b1==b2,输出true;
  • 第6行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,Long的equals方法将b进行装箱,但装箱所得到的是类型为Integer的对象o,因为o与long200为不同的类型的对象,所以输出false;

程序运行的结果为:

[java]   view plain  copy
  1. 200
  2. true
  3. false

因而,第二种推测是正确,即在+运算时,会将封装类进行拆箱。

陷阱

陷阱1

[java]   view plain  copy
  1. Integer integer100=null;
  2. int int100=integer100;

这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

陷阱2

[java]   view plain  copy
  1. Integer i1=100;
  2. Integer i2=100;
  3. Integer i3=300;
  4. Integer i4=300;
  5. System.out.println(i1==i2);
  6. System.out.println(i3==i4);

因为i1、i2、i3、i4都是Integer类型的,所以我们想,运行结果应该都是false。但是,真实的运行结果为“System.out.println(i1==i2);”为 true,但是“System.out.println(i3==i4);”为false。也就意味着,i1与i2这两个Integer类型的引用指向了同一个对象,而i3与i4指向了不同的对象。为什么呢?不都是调用Integer.valueOf(int i)方法吗?

让我们再看看Integer.valueOf(int i)方法。

[java]   view plain  copy
  1. /**
  2. * Returns a <tt>Integer</tt> instance representing the specified
  3. * <tt>int</tt> value.
  4. * If a new <tt>Integer</tt> instance is not required, this method
  5. * should generally be used in preference to the constructor
  6. * {@link #Integer(int)}, as this method is likely to yield
  7. * significantly better space and time performance by caching
  8. * frequently requested values.
  9. *
  10. * @param  i an <code>int</code> value.
  11. * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
  12. * @since  1.5
  13. */
  14. public static Integer valueOf(int i) {
  15. if(i >= -128 && i <= IntegerCache.high)
  16. return IntegerCache.cache[i + 128];
  17. else
  18. return new Integer(i);
  19. }

我们可以看到当i>=-128且i<=IntegerCache.high时,直接返回IntegerCache.cache[i + 128]。其中,IntegerCache为Integer的内部静态类,其原码如下:

[java]   view plain  copy
  1. private static class IntegerCache {
  2. static final int high;
  3. static final Integer cache[];
  4. static {
  5. final int low = -128;
  6. // high value may be configured by property
  7. int h = 127;
  8. if (integerCacheHighPropValue != null) {
  9. // Use Long.decode here to avoid invoking methods that
  10. // require Integer's autoboxing cache to be initialized
  11. int i = Long.decode(integerCacheHighPropValue).intValue();
  12. i = Math.max(i, 127);
  13. // Maximum array size is Integer.MAX_VALUE
  14. h = Math.min(i, Integer.MAX_VALUE - -low);
  15. }
  16. high = h;
  17. cache = new Integer[(high - low) + 1];
  18. int j = low;
  19. for(int k = 0; k < cache.length; k++)
  20. cache[k] = new Integer(j++);
  21. }
  22. private IntegerCache() {}
  23. }

我们可以清楚地看到,IntegerCache有静态成员变量cache,为一个拥有256个元素的数组。在IntegerCache中也对cache进行了初始化,即第i个元素是值为i-128的Integer对象。而-128至127是最常用的Integer对象,这样的做法也在很大程度上提高了性能。也正因为如此,“Integeri1=100;Integer i2=100;”,i1与i2得到是相同的对象。

对比扩展中的第二个实验,我们得知,当封装类与基础类型进行==运行时,封装类会进行拆箱,拆箱结果与基础类型对比值;而两个封装类进行==运行时,与其它的对象进行==运行一样,对比两个对象的地址,也即判断是否两个引用是否指向同一个对象。

转载于:https://my.oschina.net/Chaos777/blog/295073

Java自动装箱与拆箱及其陷阱相关推荐

  1. Java自动拆装箱面试_跟王老师学泛型(二):Java自动装箱与拆箱

    Java 自动装箱与拆箱(Autoboxing and unboxing) 主讲教师:王少华 QQ群:483773664 学习目标: 掌握Java 基本数据对应的包装类 掌握Java 自动装箱与拆箱 ...

  2. 【转】java 自动装箱与拆箱

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

  3. Java 自动装箱与拆箱

    Java 自动装箱与拆箱 装箱就是自动将基本数据类型转换为包装器类型(int–>Integer):调用方法:Integer 的 valueOf(int) 方法 拆箱就是自动将包装器类型转换为基本 ...

  4. java的自动装箱_详解Java 自动装箱与拆箱的实现原理

    详解Java 自动装箱与拆箱的实现原理 发布于 2020-7-4| 复制链接 本篇文章主要介绍了详解Java 自动装箱与拆箱的实现原理,小妖觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小妖 ...

  5. java list装箱,Java 自动装箱和拆箱

    Java 自动装箱和拆箱 在本教程中,我们将借助示例学习Java自动装箱和拆箱. Java自动装箱-包装器对象的原始类型 在自动装箱中,Java编译器会自动将原始类型转换为其相应的包装器类对象.例如, ...

  6. Java进阶(三十七)java 自动装箱与拆箱

    java 自动装箱与拆箱是jdk1.5以后才引入的新的内容.java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的(在这种情况下包装称为装箱,解包装称为拆箱): 其实按照我自己的理解自动 ...

  7. Java自动装箱与拆箱

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  8. java自动装箱和拆箱_关于java自动装箱和自动拆箱

    自动装箱和拆箱是一个老生常谈的问题了,今天我们谈一下我对这两个概念的理解. 一.自动装箱 java中一共有八种基本类型的数据,对于这些基本类型的数据都有一个对应的包装器类型.比如int--Intege ...

  9. 自动装箱自动拆箱java,自动装箱?拆箱?==问题?详解java面试常见的一个问题...

    1:前言 相信大家都在面试中都被问到过一个问题,这个问题也是近年来面试官刁难人比较常见的一个问题,所以也被大家所熟知了,本质上也很简单,但是也是非常基础的一个题目. Integer a = 100; ...

最新文章

  1. PL/SQL第五章 Order by排序
  2. 一件有趣的事:用Python爬了自己的微信朋友圈
  3. 结对项目-四则运算 “软件”之升级版
  4. asp.net中使用CKEditor
  5. 编译Tomcat9源码及tomcat乱码问题解决
  6. Linux下实用的查看内存和多核CPU状态命令
  7. 【Linux基础 11】vi和vim编辑器的使用
  8. emulator教程 lbochs pc_bochs 开启调试选项
  9. BroadcastChannel页面间通讯
  10. 计算机体系结构_计算机体系结构知识笔记
  11. 丢失Android系统库或者Conversion to Dalvik format failed with error 1错误的解决
  12. 代码整洁之道 php,关于代码整洁之道的详细介绍
  13. Excel之VBA简单宏编程
  14. html重复渐变包括,html – CSS:当设置为tbody / thead时,在Chrome中重复的渐变
  15. python项目之杠子老虎鸡虫
  16. u深度制作linux启动盘制作工具,u深度u盘启动盘制作工具 v3.1.15.316
  17. 单点登录(SSO)解决方案介绍
  18. Java的笔记开源软件_jnote
  19. 专访阿里巴巴毕玄:异地多活数据中心项目的来龙去脉
  20. DuerOS智能设备激活数破亿!百度补贴上亿造节再添一把火

热门文章

  1. 学Ruby开发的几个好网站
  2. 跟进table_cache参数
  3. Java 面试必考难点,这一个教程全搞定
  4. 排名前20的网页爬虫工具,超多干货
  5. java语言如何跳转界面_在java中spring mvc页面如何跳转,详细图解
  6. aix oracle 内存限制,请教 AIX 与 Linux 中,怎样分析Oracle的内存占用?
  7. 文件上传下载-修改文件上传大小
  8. Nginx系统环境准备
  9. RDB 文件的优势和劣势
  10. 千鸟弹幕机器人_千鸟熊猫TV直播弹幕机器人软件