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.访问,存 ...
随机推荐
- 【C#】wpf添加gif动图支持
原文:[C#]wpf添加gif动图支持 1.nuget里下载XamlAnimatedGif包,然后安装. 2.添加XamlAnimatedGif包的命名空间:xmlns:gif="https ...
- 关于阿里云centos7安装svn,客服端无法链接的问题
阿里云的centos7的版本中,通过yum安装了subversion之后,svn客服端无法链接svn服务器. 首先确定服务器的安全组策略中的3690端口是否打开 然后确定svnserve配置是否正确, ...
- VS.NET版本与VC版本对应关系
VC++编译器版本 IDE版本 VC6 VC6.0 VC7 VS2003 VC8 VS2005 VC9 VS2008 VC10 VS2010 VC11 VS2012 VC12 VS2013 VC14 ...
- LockWindowUpdate的函数的用法(不忽略消息,只是暂时不响应,但WM_SETREDRAW根本不接受重绘消息)
Application.ProcessMessages;LockWindowUpdate(Self.Handle); //锁住当前窗口 LockWindowUpdate(0)//解除锁定窗口 Loc ...
- Linux下C/C++帮助手册安装方法
1. 安装C的帮助手册 如果你使用的Linux发行版, 默认没有安装C语言的库函数MAN手册, 使用下面的方法解决: # sudo apt-get install manpages # sudo a ...
- Topshelf结合Quartz.NET实现服务端定时调度任务
这周接受到一个新的需求:一天内分时间段定时轮询一个第三方WebAPI,并保存第三方WebAPI结果. 需求分析:分时段.定时开启.定时结束.轮询.主要工作集中在前三个上,轮询其实就是个Http请求,比 ...
- qobject_cast<QPushButton*>(sender()) 简化信号与槽的编写(sender()取得发信号的对象后,就取得了它的全部信息,为所欲为)
当你觉得写代码是一件重复性极高的工作时,这时你就应该考虑换个方式来实现了. 提高代码效率,减少代码量. 代码片: void Widget::onClicked() { QPushButton* but ...
- 介绍两种Timer定时器的使用
第一种, 直接实例化Timer类,设置时间间隔,到达时间后执行想要执行的事件.代码示例: using System; using System.Collections.Generic; using S ...
- 因为 'PRIMARY' 文件组已满。请删除不需要的文件、删除文件组中的对象、将其他文件添加到文件组或为文件组中的现有文件启用自动增长,以便增加可用磁盘空间
导致你的问题的,应该有2种可能性: 1.存放你的primary文件组的磁盘,已经满了: use master--你的数据库名称 go --看看你的primary组里的文件 select ds.name ...
- Spring Type Conversion(Spring类型转换)
Spring Type Conversion(Spring类型转换) 1:概述: Spring3引入了core.convert包,提供了通用类型转换系统,定义了实现类型转换和运行时执行类型的SPI. ...