equals()方法详解
equals()
方法是用来判断其他的对象是否和该对象相等.
equals()方法在object类中定义如下:
public boolean equals(Object obj) { return (this == obj); }
很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。
不过,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。这些(子)类的equals()方法已经不再是比较地址了。而进行的是内容的比较。总的来说,基本类型是进行值的比较。它的性质有:
- 自反性(reflexive)。对于任意不为
null
的引用值x,x.equals(x)
一定是true
。自己一定等于自己 - 对称性(symmetric)。对于任意不为
null
的引用值x
和y
,当且仅当x.equals(y)
是true
时,y.equals(x)
也是true
。双向一致 - 传递性(transitive)。对于任意不为
null
的引用值x
、y
和z
,如果x.equals(y)
是true
,同时y.equals(z)
是true
,那么x.equals(z)
一定是true
。三者循环一致 - 一致性(consistent)。对于任意不为
null
的引用值x
和y
,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)
要么一致地返回true
要么一致地返回false
。结果恒定 - 对于任意不为
null
的引用值x
,x.equals(null)
返回false
。引用不为空
对于Object
类来说,equals()
方法在对象上实现的是差别可能性最大的等价关系,即,对于任意非null
的引用值x
和y
,当且仅当x
和y
引用的是同一个对象,该方法才会返回true
。即,比较引用/地址。
需要注意的是当equals()
方法被override时,hashCode()
也要被override。按照一般hashCode()
方法的实现来说,相等的对象,它们的hashcode一定相等。但是hashcode相等的两个对象,并不一定equals。
hashcode() 方法详解
hashCode()
方法给对象返回一个hash code值。这个方法被用于hash tables,例如HashMap。
它的性质是:
- 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用
hashCode()
方法,该方法必须始终如一返回同一个integer。即hashCode是恒定的。 - 如果两个对象根据
equals(Object)
方法是相等的,那么调用二者各自的hashCode()
方法必须产生同一个integer结果。equals()可推出hashcode相同。 - 并不要求根据
equals(java.lang.Object)
方法不相等的两个对象,调用二者各自的hashCode()
方法必须产生不同的integer结果。然而,程序员应该意识到对于不同的对象产生不同的integer结果,有可能会提高hash table的性能。
大量的实践表明,由Object
类定义的hashCode()
方法对于不同的对象返回不同的integer。
在object类中,hashCode定义如下:
public native int hashCode();
说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double等这些类都是覆盖了hashcode()方法的。例如在String类中定义的hashcode()方法如下:
public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31 * h + val[off++]; } hash = h; } return h; }
解释一下这个程序(String的API中写到):s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂(空字符串的哈希码为 0)。
想要弄明白hashCode的作用,必须要先知道Java中的集合。
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。这里就引出一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,初学者可以简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。简而言之,在集合查找时,hashcode能大大降低对象比较次数,提高查找效率!
Java对象的eqauls方法和hashCode方法是这样规定的:
1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
想象一下,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不同,则A和B存入HashMap时的哈希码计算得到的HashMap内部数组位置索引可能不同,那么A和B很有可能允许同时存入HashMap,显然相等/相同的元素是不允许同时存入HashMap,HashMap不允许存放重复元素。
2、如果两个对象的hashCode相同,它们并不一定相同。
也就是说,不同对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用原则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。
所以,Java对于eqauls方法和hashCode方法是这样规定的:
1.如果两个对象相同,那么它们的hashCode值一定要相同;
2.如果两个对象的hashCode相同,它们并不一定相同(这里说的对象相同指的是用eqauls方法比较)。
如不按要求去做了,会发现相同的对象可以出现在Set集合中,同时,增加新元素的效率会大大下降。
3.equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反过来,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了;在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时,Hashcode()方法根据String类的重写代码的分析,也可知道hashcode()返回结果也会相等。以此类推,可以知道Integer、Double等封装类中经过重写的equals()和hashcode()方法也同样适合于这个原则。当然没有经过重写的类,在继承了object类的equals()和hashcode()方法后,也会遵守这个原则。