今天遇见一个有趣的例子:
1 | Integer a = 40; |
这是为啥?原来是Java中存在一个名为IntegerCache
的静态类,其中的静态代码块会创建一个包含-128~127的整型数组,若使用Integer
创建的值范围在-128~127之间,就直接使用该常量池中的数据,否则就新建一个对象。基于此,重新复习了一遍Java中的基本数据类型包装类与自动装箱、自动拆箱的机制。
一、为什么要有包装类
Java是面向对象语言,其宗旨是一切皆对象。可是,Java中包含着八大基本数据类型,它们不具有对象的特征。于是,便有了包装类,让基本数据类型具有对象的特性,同时拥有一系列自己的属性与操作方法。
二、装箱?拆箱?
看如下定义:
1 | Integer a = new Integer(50); // (1) |
如上所示,Integer
是一个类,理应通过方法(1)使用new
关键字创建一个对象,而方法(2)中却可以直接让一个基本数据类型赋值给一个类,方法(3)则将一个对象赋值给了基本数据类型。这之中就使用了装箱(方法2,调用了Integer.valueOf()
方法)和拆箱(方法3,调用了Integer.intValue()
方法)。其他基本数据类型的拆箱装箱也类似。
三、丰富的例子
1 | Integer i1 = 40; |
解释一下上面的6个例子的结果。
- (1)使用自动装箱调用了
Integer.valueOf()
方法,其中,传入的int
值范围在-128~127之间,则直接从常量池中取对象,故i1
和i2
指向了同一个对象,结果相等。 - (2)
+
操作符不适用于类Integer
,故i2 + i3
的过程中使用了自动拆箱得到结果为40
,随后又与对象i1
进行比较,i1
使用了自动拆箱为40
,进行数值比较,结果相等。 - (3)注意,常量池的使用触发条件是调用了
Integer.valueOf()
方法,若使用new
新建对象,则不会使用常量池中的对象,而是重新建立一个对象,故i1
与i4
指向的是不同的对象,结果不相等。 - (4)与(3)同理,都是新建了一个对象。
- (5)与(2)同理,
+
操作符是基本数据类型操作符,后面的表达式i5 + i6
使用了自动拆箱,得到结果为40
,再使i4
自动拆箱,进行数值比较,结果相等。 - (6)与(5)同理。
四、有必要提一下==操作符与Object类的equals()方法
- ==:判断两个对象的地址是否相等,即判断两个对象是否是同一个对象。若==两端是基本数据类型,则比较它们的值是否相等,若是引用类型,则比较它们的内存地址是否相等。
- equals()方法:同样是判断两个对象的地址是否相等。但该方法可以被重写(覆盖),用于比较值是否相等(如
String
类中就重写了equals()
方法,当两个字符串对象的内容是一样的,就返回true
)。
五、那就有必要顺带一提hashCode()方法
为啥重写了equals()
方法就必须重写hashCode()
方法?
众所周知,hashCode()
是用于生成散列码,用于作为散列表的插入与查找标记。当两个对象的hashCode()
相同时,表明它们位于桶中的同一个位置,却不代表它们是同一个对象,因为桶中的该位置还会有一个链表用于解决哈希冲突。此时会再调用equals()
方法判断是否真的是同一个对象,若不是,则会在链上继续查找(或插入),若是,则返回(或不插入新的)。故两个相同的对象具有相同的hashCode
,也令equals()
返回值为true
;但具有相同hashCode
的两个对象不一定是同一对象,也即equals()
可能返回false
。故重写了euqals()
方法也需要重写hashCode()
方法。