在初学java时,会遇到下面的代码
两次输出的结果依次为:false true
为什么会出现这种情况? == 和equals之间到底有什么不同?作为程序员,必须要搞明白。
一、关系运算符“==”到底是什么?首先看一个例子:
输出的结果依次为:true false true。
m == n 结果为true,这个很容易理解,因为变量n和变量m存储的都是3,那一定相等。那为什么s1==s2结果不相等呢?要理解这个只需要理解基本类型数据变量和非基本类型数据变量的区别。
java中的8中基本类型数据变量:
整数:byte(1 byte) , short(2 byte) , int(4 byte), long(8 byte)
浮点数:float(4 byte), double(8 byte)
字符型:char(2 byte)
布尔型:boolean(JVM中没有明确规定其所占的大小,只能取字面值“true”,"false");
对于这8中基本类型数据变量,变量直接存储的是“值”,因此在用关系运算符"=="来进行比较时,比较的就是“值”本身。
也就是说,
int m=3;
int n=3;
变量m和变量n存储的都是“3”这个数值,所以用“==”比较的结果就是true.
而对于非基本类型的变量,也称为引用类型的变量。在存储的时候,不是直接存储“值”本身,而是与其关联的对象在内存中的地址。比如:
String s1;
这句话声明了一个引用类型的变量,此时它并没有和任何对象关联。
而通过 new String("hello")来产生一个对象(也称为类String的一个实例),并将这个对象和s1进行关联:
s1 = new String("hello").
那么s1指向了一个对象(s1也被称为对象的引用),此时变量s1中存储的是它指向的对象在内存中的地址,并不是“值”本身,也就是说并不是直接存储的是字符串“hello”.
因此第一次s1==s2的结果为false,因为它们分别指向了不同的对象,也就是说他们实际存储的内存地址不同。
而在第二次比较时,让s1和s2分别都指向了s,所以结果为true.
二、equals比较的又是什么?
由于每个类都继承自Object,所以我们首先看一下Object类中equals的实现:
可以看到,在Object中,equals是比较两个对象的引用是否相等,即是否指向同一个对象。
那在开篇中,String s1 = new String("hello");String s2 = new String("hello");s1.equals(s2) 为true。这又是为什么呢?
那我们需要知道String中equals的方法实现:
可以看到,String类中对equals方法进行了重写,用来比较指向的对象所存储的字符串是否相等。
其他的一些类,如Integer,Float,Double都是对equals方法进行了重写,用来比较指向的对象所存储的字符串是否相等。
总结一下:
①对于==,如果是基本类型的变量,则直接比较的是所存储的“值”是否相等;
对于非基本类型的变量,则比较的是所存储的对象的地址
②对于equals,注意,equals方法不能作用于基本类型的对象;
如果没有对equals方法进行重写,则比较的是所存储的对象的地址;
如果像Sting,Date这种对equals方法进行了重写,则比较的是所指向的对象的内容。
三、如何对equals方法重写?
在重写equals方法时,需要满足equals自身的特性:
①自反性:对于任意的引用值x,x.equals(x)一定为true
②对称性:对于任意的引用值x和y,当x.equals(y)为true时,y.equals(x)也一定为true.
③传递性:对于任意的引用值x、y、z,当x.equals(y)为true,并且y.equals(z)也为true是,则x.equals(z)也一定为true.
④一致性:对于任意的引用值x和y,对于equals比较的对象信息没有被修改,对于多次调用x.equals(y)要么一致的true,要么一致的为false。
⑤非空性:对于任意的引用值x,x.equals(null)一定为false。
当我们重写equals方法时,只需要验证②③④这三点即可。
四、hashCode原理
举一个比较简单的例子来说明这个算法的运算公式:
假设定义一个长度为8的Hash表,也可以理解为一个长度为8的数组。在这个hash表中存放数字:
假设我们的Hash函数为:
HashCode = X%8 对8求余
其中X就是我们要放入Hash表中的数字,而这个函数返回的Hashcode就是hash码。
假设我们有下面10个数字需要依次存入到这个Hash表中:
11,23,44,9,6,32,12,45,57,89
通过上面的Hash函数,我们可以得到分别对应的Hash码:
11--3,23---7,44---4,9---1,6---6,32---0,12---4,45---5,57---1,89---1;
计算出来的hash码分别代表该数字应该存放到Hash表中的哪个位置。如果该格子中已经有数字存在了,那么就以链表的方式将数字依次存在该格子中。
当向集合中插入对象时,如何判断在集合中是否已经存在该对象了呢?
也许大多数人都会考虑到调用equals方法来逐个进行比较,这个方法确实可行。但如果集合中很多的数据,如果采用equals方法进行逐一去比较,效率必然低下。此时hashCode的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashCode值。实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashCode值,如果table中没有该hashCode值,它就可以直接存进去,不用再进行任何比较了,如果存在该hashCode值,就调用它的equals方法与新元素进行比较,相同的话就不用存了,不相同就散列其他的地址,这样一来调用equals方法的次数就大大降低了。Java中的hashCode方法就是根据一定的规则将与对象相关的信息(对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
在java中,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的集合有HashSet,HashTable,HashMap。在使用这几个类进行存储数据时,它会自动调用hashCode方法来比较两个对象是否相等。
在往HashSet集合放置元素时,会根据其hashCode来判断两个元素是否一样,如果是一样,将后者覆盖前者。而hashCode默认是比较其地址值。
既然自定义类重写了equals方法,说明类的设计者不希望通过内存地址来比较两个对象是否相等。
而Object的hashCode也是对象的内存地址,既然不希望通过地址比较对象,那么以内存地址作为hashCode也就没有实际使用意义了。
五、重写equals和重写hashCode
如果两个对象equals相等,那么hashCcde一定相等。反之,则不一定。在重写equals时,一定要重写hashCode方法。