深入了解基本类型和包装类

简单说架构

发布时间: 19-06-0415:21

了解基本数据类型和包装类的区别,最终了解:什么时候应该使用基本数据类型,什么时候应该使用包装类

开始

我们知道,Java中有八种基本数据类型,它们又对应八种包装类型。那么,回想一下你在代码中使用基本数据类型或包装类型时,选择标准是什么?有没有标准呢?今天,我们就来了解一下包装类型。

不妨也先这道题开始思考。输出结果是什么?

基本知识

基本数据类型和包装类

Java有八种基本类型,byte, short, int, long, float, double, char, boolean。

对应八种包装类,Byte, Short, Integer, Long, Double, Character, Boolean。

以int为例,创建一个基本类型的变量时,基本类型如下:

int a = 0;

包装类型如下:

Integer a = new Integer(0);

内存方式

基本类型属于原始数据类型,变量中存储的就是原始值。包装类型属于引用数据类型,变量中存储的是存储原始值的地址的引用。

我们先不考虑内存空间各种不同区域对应不同的用途,将其看做一个整体,思考以下语句的内存分配过程。

int a = 0;

分配一块内存,即为a,存入的值为0。

Integer b = new Integer(0);

分配一块内存,我们称之为内存1,存储0,再分配一块内存,我们称之为内存2。内存2中存放的是指向内存1的引用。

==和equals

基本类型的比较要用==,包装类型的比较要用equals。==是Java本身的一个操作符,它的作用是比较两个变量在内存中的地址,相同为true,不同为false。equals则是一个方法。Java中,所有的类都有一个根级父类,Ojbect,包括包装类。在不重写的情况下,直接equals实际上就是在调用Ojbect类的equals方法。而Ojbect类的equals方法,也是使用==来作判断的。换句话说,如果没有经过重写,equals和==的结果是一致的。而如果我们想要达到某些其它目的,比如说,我们希望在地址不同,但是值相同的情况下,也能返回true,就需要自己重写equals方法。

看到这里,产生了一个疑惑,如下:

int a = 0;

int b = 0;

System.out.println(a == b);

true还是false?

初看觉得是true,再想觉得是false,程序运行结果是true。

疑惑:两个变量的值虽然是相同的,但是,它们在内存中的地址应该是不同的,只是其中的值相同。按照==的运算规则,理由得到false的结果(内存中的地址不同),结果却是true,为什么?

事实:当定义b时,JVM会先检查内存中是否已经有了0这个值,如果没有,则创建,如果有(如之前已经执行过int a = 0),则不会再创建,而是直接将变量b指向变量a所有内存的地址,就好像执行的语句是int b = a。换句话说,a和b最终指向的内存空间,其实还是一致的。

基于上述事实,又产生了一个疑惑,如下:

int a = 0;

int b = 0;

b = b + 1;

System.out.printlt(a == 2);

疑惑:因为变量b实质上指向的是变量a的内存地址,所以,对b的修改,实质上就是对a的修改,输出结果应该是true,但实际上false。

事实:这里,又有一个误解。当执行b = b + 1时,JVM事实上并不是直接去对变量a的内存地址中的值做操作。事实上是,它先取出了a的值,为1。然后进行 +1 操作,得到2。这时候,JVM并不会直接把变量a内存地址中的值直接改成2,而是会在内存中寻找是否有2,有就将b指向相应地址,没有就把b内存地址中的值修改为2。

自动装箱和自动拆箱

在实际使用时,通过我们在定义包装类型的定义时,不会使用new关键字,而是如下:

Integer a = 0;

思考这条语句为什么成立?难道是说字面量0就相当于一个Integer对象吗?当然不是,这其实是Java为了方便,为我们提供了自动装箱的机制。事实上,这条语句相当于:

Integer a = Integer.values(0);

values是Integer 类提供一个静态方法,返回值是一个Integer实例。

和自动装箱相反,当你把一个包装类型的变量赋值到一个基本类型的变量时,Java会进行自动拆箱的过程,执行intValue方法。

Integer a = 0;(自动装箱)

int b = a;(自动拆箱)

缓存机制

在执行Integer a = new Integer(0)时,JVM会先创建一个对象,值为0,再把这个对象的引用赋值给a。基于节约内存和效率等的考虑,Java对包装类的values方法做了一些优化。如Integer,在执行Integer a = Integer.values(0)时,Java会在缓存中直接找到之前创建好的缓存,直接把0相应的引用给a。

又产生了之前的疑惑吗?其实Java的操作还和之前是一样的。所以,并不会出现不合理的问题。

观察源码发现,Boolean的缓存是true,false,Byte、Short、Integer、Long的缓存都是-128到127。Character是缓存是0~127对应的字符。Float,Double没有缓存。为什么没有?你也这样疑惑吗?

其实答案很简单,思考一下缓存的意义就可以想到,为了使缓存真正有效果,应该把最常用的一部分数据放到缓存里。但是,对于小数来说,选定一个集合,其中元素的个数是无限的。所以,Java可能在想这个问题的时候,实在是想不出来,应该以什么标准判断,把什么元素放到缓存里,于是,就放弃了,干脆不要缓存了。

初始化

基本类型和包装类型的另外一个区别时,在用作类变量时,在初始化的时候,包装类型会被初始化成null,基本类型会被初始化成特定的值,如int为0,boolean为false等。

思考一下这个差距,会带来什么样的影响?

使用原则

最后我们来整理一下基本类和包装类在实际使用时,应该遵循哪些原则?

1. 尽量使用values方法。最大可能使用缓存,提高程序的效率。

2. 类变量使用包装类。想象有一个和数据库表对应的实体类,如果你使用的基本数据类型,在插入时,可能会插入一些让你意想不到的初始值。

3. 方法的参数要使用包装类。使用包装类意味着你在调用时,可以令若干个参数为null。null是无意义的。但是如果你使用了基本数据类型,那么,你将不得不传入一个值,即使这个值对你来说,没有什么意义。

4. 方法的返回值要根据是否可为null来确定使用包装类还是基本类。当一个方法的返回值,一定不会出现null的情况时,推荐使用基本类来作为返回值。这样,调用者在拿到这个方法的返回值时,就不必担心它是为null了。

5. 方法内部(局部变量)使用基本类型。基本类型的时间效率和效率上来说,都是要优于包装类的。所以,在方法内部,能使用基本类型尽量不要使用包装类。

6. 小数的计算。严格来说,这条不属于基本类型和包装类的内容,但是,这里顺便提交,当涉及到小数的计算时,要考虑到计算的精度问题,可以使用BigDecimal,也可以通过缩小计量单位,达到化零为整的目的,这个,根据具体场景来确定。

举报/反馈