JAVA包装类解析和面试陷阱分析
包装类
什么是包装类
虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。 沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。这种借助于非面向对象技术的做法有时也会带来不便。比如:
- 编码过程中只接收对象的情况,比如List中只能存入对象,不能存入基本数据类型;比如一个方法的参数是Object时,不能传入基本数据类型,但可以传入对应的包装类; 比如泛型等等。
- 基本数据类型没有toString()方法等
Java中对每种基本类型都有一个对应的包装类:
- byte -> Byte
- short -> Short
- int -> Integer
- long -> Long
- float -> Float
- double -> Double
- boolean -> Boolean
- char -> Character
其中需要注意int对应的是Integer,char对应的Character,其他6个都是基本类型首字母大写即可。
[作者:Joey先生] 每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的方法。包装类对象一经创建,其内容(所封装的基本类型数据值)不可改变(类似于String类,可看源码)。
基本数据类型和包装类的区别
- 定义不同。包装类属于对象,基本数据类型不是
- 声明和使用方式不同。包装类使用new初始化,有些集合类的定义不能使用基本数据类型,例如 ArrayList<Integer>
- 初始值不同。包装类默认值为null,基本数据类型则不同的类型不一样(具体见上表)
- 存储方式和位置不同,从而性能不同。基本数据类型存储在栈(stack)中,包装类则分成引用和实例,引用在栈(stack)中,具体实例在堆(heap)中。可以通过程序来验证速度的不同。
拆箱/装箱
基本类型和对应的包装类可以相互装换:
- 由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象;
例如:Integer i = Integer.valueOf(1); //手动装箱
Integer j = 1; //自动装箱 - 包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象重新简化为 int。
Integer i0 = new Integer(1);
int i1 = i0; //自动拆箱
int i2 = i0.intValue(); //手动拆箱[作者:Joey先生] jdk5.0开始增加自动装箱/拆箱
面试陷阱
介绍完包装类的基本情况,下面重点讲几个面试/笔试中的常见陷阱。
- 关于equals
double i0 = 0.1;
Double i1 = new Double(0.1);
Double i2 = new Double(0.1); System.out.println(i1.equals(i2)); //true 2个包装类比较,比较的是包装的基本数据类型的值
System.out.println(i1.equals(i0)); //true 基本数据类型和包装类型比较时,会先把基本数据类型包装后再比较注意equals方法比较的是真正的值。
基本数据类型和包装类比较时,会先把基本数据类型包装成对应的包装类型,再进行比较。这一点可以通过查看.class字节码来证明:[作者:Joey先生]为什么需要这么做? 我们来查看JDK中Double包装类的equals方法:
可以看到equals方法中的参数是一个Object对象,而基本数据类型并不是一个对象类型,所以需要先将基本数据类型包装成对应的包装类后才能作为参数传入equals方法中。由此也侧面验证了包装类的必要性。 - 关于==(双等号)比较
==(双等号)比较的陷阱比较多。对此有必要先讲一个前置知识点:
对于基本数据类型,==(双等号)比较的是值,而对于包装类型,==(双等号)比较的则是2个对象的内存地址。
[作者:Joey先生] OK,下面开始讲题:
double i0 = 0.1;
Double i1 = new Double(0.1);
Double i2 = new Double(0.1);
System.out.println(i1 == i2); //false new出来的都是新的对象
System.out.println(i1 == i0); //true 基本数据类型和包装类比较,会先把包装类拆箱
注意一点规律,new出来的都是新的对象。2个新的对象内存地址不同,那么==号比较的结果肯定是false。
基本数据类型和包装类型比较时,会先把包装类拆箱再进行值比较(和equals是反的)。这个也可以通过查看字节码来证明:
为什么要这么做?因为如果反过来把基本数据类型进行包装后再比较,那么2个包装类对象的内存地址是不一样的,==号比较肯定一直未false!
继续看另一个陷阱:
Double i1 = Double.valueOf(0.1);
Double i2 = Double.valueOf(0.1);
System.out.println(i1 == i2); //false valueOf方法内部实际上也是new
注意这里i1和i2是用包装类的静态方法valueOf来构造的。我们看一下Double.valueOf的源码:
可以看到,valueOf内部也是用的new方法来构造对象的。2个new出来的对象,内存地址肯定是不一样的。
那么问题来了,下面的代码运行结果却让人大跌眼镜:
System.out.println(Integer.valueOf(1) ==Integer.valueOf(1)); //true
System.out.println(Integer.valueOf(999) ==Integer.valueOf(999)); //false
是不是感觉不会再爱了?
没关系,我们还是从源码来分析问题。查看Integer.valueOf方法的源码:
可以看到,当i的值在low和high范围内是,返回的是一个cache里的Integer对象。
我们继续追踪IntegerCache.cache:
[作者:Joey先生] 可以看到从-128到到127之间的值都被缓存到cache里了。这个叫做包装类的缓存。
Java对部分经常使用的数据采用缓存技术,在类第一次被加载时换创建缓存和数据。当使用等值对象时直接从缓存中获取,从而提高了程序执行性能。(通常只对常用数据进行缓存)
Integer默认缓存是-128到127之间的对象,上限127可以通过修改JVM参数来更改。
回到我们的示例代码中,可以看到Integer.valueOf(1)是会直接从缓存中去取被缓存的同一个对象,所以比较的值是true。而Integer.valueOf(999)是缓存范围外的值,那么就会new一个新的对象出来,所以比较的结果是false。
实际上,包装类中整数型的类型基本上都有缓存数据。可以通过阅读源码了解。不过只有Integer类可以通过修改JVM参数来更改缓存上限。
其他类型的缓存范围为:
- Integer类型有缓存-128-127的对象。缓存上限可以通过配置jvm更改
- Byte,Short,Long类型有缓存(-128-127)
- Character缓存0-127
- Boolean缓存TRUE、FALSE
尤其需要特别注意的是,只有valueOf方法构造对象时会用到缓存,new方法等不会使用缓存!
了解了缓存机制,那么再看下面这道题目:
Integer i4 = Integer.valueOf(1);
Integer i5 =1;
System.out.println(i4 == i5); //true Integer i7 = Integer.valueOf(999);
Integer i8 = 999;
System.out.println(i7 == i8); //false
是不是觉得脑仁疼?
没关系,我们看一下上面自动包装时的class字节码:
发现什么了? 自动包装时实际上还是调用的valueOf方法。而valueOf方法用到了缓存池。
通过今天的学习,相信大家已经体验到了如何从“根源”上去分析和解决问题。养成阅读字节码和源代码的好习惯,对我们的成长是非常有帮助的。
我们要学习的是“方法”而不是“答案”。
也欢迎大家加Joey老师QQ 2057388734 来一起讨论学习。
JAVA包装类解析和面试陷阱分析的更多相关文章
- Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现
视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- java基础解析系列(九)---String不可变性分析
java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
线程与操作系统中线程(进程)的概念同根同源,尽管千差万别. 操作系统中有状态以及状态的切换,Java线程中照样也有. State 在Thread类中有内部类 枚举State,用于抽象描述Java线程的 ...
- Java web轻量级开发面试教程的前言
本文来是从 java web轻量级开发面试教程从摘录的. 为什么要从诸多的Java书籍里选择这本?为什么在当前网络信息量如此大的情况下还要买这本书,而不是自己通过查阅网络资料学习?我已经会开发Java ...
- Java容器解析系列(0) 开篇
最近刚好学习完成数据结构与算法相关内容: Data-Structures-and-Algorithm-Analysis 想结合Java中的容器类加深一下理解,因为之前对Java的容器类理解不是很深刻, ...
- Java Web文件上传原理分析(不借助开源fileupload上传jar包)
Java Web文件上传原理分析(不借助开源fileupload上传jar包) 博客分类: Java Web 最近在面试IBM时,面试官突然问到:如果让你自己实现一个文件上传,你的代码要如何写,不 ...
- Java常用英语汇总(面试必备)
Java常用英语汇总(面试必备) abstract (关键字) 抽象 ['.bstr.kt] access vt.访问,存 ...
随机推荐
- 自定义LISTBOX内子项为checkbox或者radio时,关于IsChecked绑定
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}&q ...
- 深入理解Amazon Alexa Skill(二)
理解skill调用 本节来更详细的讨论alexa是如何确定调用哪个skill的. 参考:https://developer.amazon.com/zh/docs/custom-skills/under ...
- C#基础加强篇---委托、Lamada表达式和事件(中)
2.Lamada表达式 C#共有两种匿名函数:匿名方法和Lamada表达式.在2.0之前的C#版本中,创建委托的唯一方法是使用命名方法.C#2.0中引入了匿名方法,匿名方法就是没有名称的方法. ...
- Python爬虫: "追新番"网站资源链接爬取
“追新番”网站 追新番网站提供最新的日剧和日影下载地址,更新比较快. 个人比较喜欢看日剧,因此想着通过爬取该网站,做一个资源地图 可以查看网站到底有哪些日剧,并且随时可以下载. 资源地图 爬取的资源地 ...
- System.Exception: ORA-12541: TNS: 无监听程序
今天在一个服务器上发布一个web服务(数据库也装在这台机器上):开发工具 Visual Studio 2008 Oracle但是部署好,浏览的时候报错了:System.Web.Services.Pro ...
- fprintf函数将格式打印到文件,非常好用(怎么没早点发现这个函数)
/* fprintf example */ #include <stdio.h> int main () { FILE * pFile; int n; ]; pFile = fopen ( ...
- SetForegroundWindow API函数还不够(好多好多解决方案,真是奇思妙想)
好多好多解决方案: var Input: TInput; begin ZeroMemory(@Input, SizeOf(Input)); SendInput(, Input, SizeOf(Inpu ...
- Visual C++ 异常(Exception)常见问题 (原文标题:A Visual C++ Exception FAQ)
Visual C++ 异常(Exception)常见问题 版权:Doug Harrison 2001 – 2007 翻译:magictong(童磊) 2011年3月 原文地址:http://membe ...
- 分布式数据库中间件 MyCat 搞起来!
关于 MyCat 的铺垫文章已经写了三篇了: MySQL 只能做小项目?松哥要说几句公道话! 北冥有 Data,其名为鲲,鲲之大,一个 MySQL 放不下! What?Tomcat 竟然也算中间件? ...
- Tido c++线段树知识讲解(转载)
线段树知识讲解 定义.建树.单点修改.区间查询 特别声明:如上的讲解说的是区间最大值 如果想要查询区间和 只需要改变一下建树和查询的代码就行了,如下 其他根据自己的需要进行修改即可