hashCode()和equals()方法可以说是Java完全面向对象的一大特色.它为我们的编程提供便利的同时也带来了很多危险.这篇文章我们就讨论一下如何正解理解和使用这2个方法.
如何重写equals()方法
如果你决定要重写equals()方法,那么你一定要明确这么做所带来的风险,并确保自己能写出一个健壮的equals()方法.一定要注意的一点是,在重写equals()后,一定要重写hashCode()方法.具体原因稍候再进行说明.
我们先看看 JavaSE 7 Specification中对equals()方法的说明:
- It isreflexive: for any non-null reference value
x
,x.equals(x)
should returntrue
.
- It issymmetric: for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and
only ify.equals(x)
returnstrue
.
- It istransitive: for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
,
thenx.equals(z)
should returntrue
.
- It isconsistent: for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or
consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified.
- For any non-null reference value
x
,x.equals(null)
should returnfalse
.
这段话用了很多离散数学中的术数.简单说明一下:
1. 自反性:A.equals(A)要返回true.
2. 对称性:如果A.equals(B)返回true, 则B.equals(A)也要返回true.
3. 传递性:如果A.equals(B)为true, B.equals(C)为true, 则A.equals(C)也要为true. 说白了就是 A = B , B = C , 那么A = C.
4. 一致性:只要A,B对象的状态没有改变,A.equals(B)必须始终返回true.
5. A.equals(null) 要返回false.
相信只要不是专业研究数学的人,都对上面的东西不来电.在实际应用中我们只需要按照一定的步骤重写equals()方法就可以了.为了说明方便,我们先定义一个程序员类(Coder):
class Coder {
private String name;
private int age;
// getters and setters
}
我们想要的是,如果2个程序员对象的name和age都是相同的,那么我们就认为这两个程序员是一个人.这时候我们就要重写其equals()方法.因为
默认的equals()实际是判断两个引用是否指向内在中的同一个对象,相当于 == . 重写时要遵循以下三步:
1. 判断是否等于自身.
if(other == this)
return true;
2. 使用instanceof运算符判断 other 是否为Coder类型的对象.
if(!(other instanceof Coder))
return false;
3. 比较Coder类中你自定义的数据域,name和age,一个都不能少.
Coder o = (Coder)other;
return o.name.equals(name) && o.age == age;
看到这有人可能会问,第3步中有一个强制转换,如果有人将一个Integer类的对象传到了这个equals中,那么会不会扔ClassCastException呢?这个担心其实是多余的.因为我们在第二步中已经进行了instanceof 的判断,如果other是非Coder对象,甚至other是个null, 那么在这一步中都会直接返回false, 从而后面的代码得不到执行的机会.
上面的三步也是<Effective Java>中推荐的步骤,基本可保证万无一失.
如何重写hashCode()方法
在JavaSE 7 Specification中指出,
"Note that it is generally necessary to override thehashCode
method whenever this method(equals) is overridden, so as to maintain the general
contract for thehashCode
method, which states that equal objects must have equal hash codes."
如果你重写了equals()方法,那么一定要记得重写hashCode()方法.我们在大学计算机数据结构课程中都已经学过哈希表(hash table)了,hashCode()方法就是为哈希表服务的.
当我们在使用形如HashMap, HashSet这样前面以Hash开头的集合类时,hashCode()就会被隐式调用以来创建哈希映射关系.稍后我们再对此进行说明.这里我们先重点关注一下hashCode()方法的写法.
<Effective Java>中给出了一个能最大程度上避免哈希冲突的写法,但我个人认为对于一般的应用来说没有必要搞的这么麻烦.如果你的应用中HashSet中需要存放上万上百万个对象时,那你应该严格遵循书中给定的方法.如果是写一个中小型的应用,那么下面的原则就已经足够使用了:
要保证Coder对象中所有的成员都能在hashCode中得到体现.
对于本例,我们可以这么写:
@Override
public int hashCode() {
int result = 17;
result = result * 31 + name.hashCode();
result = result * 31 + age;
return result;
}
其中int result = 17你也可以改成20, 50等等都可以.看到这里我突然有些好奇,想看一下String类中的hashCode()方法是如何实现的.查文档知:
"Returns a hash code for this string. The hash code for aString
object is computed as
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
usingint
arithmetic, wheres[i]
is theith
character of the string,n
is the length of the string, and^
indicates exponentiation. (The
hash value of the empty string is zero.)"
对每个字符的ASCII码计算n - 1次方然后再进行加和,可见Sun对hashCode的实现是很严谨的. 这样能最大程度避免2个不同的String会出现相同的hashCode的情况.
重写equals()而不重写hashCode()的风险
在Oracle的Hash Table实现中引用了bucket的概念.如下图所示:
从上图中可以看出,带bucket的hash table大致相当于哈希表与链表的结合体.即在每个bucket上会挂一个链表,链表的每个结点都用来存放对象.Java通过hashCode()方法来确定某个对象应该位于哪个bucket中,然后在相应的链表中进行查找.在理想情况下,如果你的hashCode()方法写的足够健壮,那么每个bucket将会只有一个结点,这样就实现了查找操作的常量级的时间复杂度.即无论你的对象放在哪片内存中,我都可以通过hashCode()立刻定位到该区域,而不需要从头到尾进行遍历查找.这也是哈希表的最主要的应用.
如:
当我们调用HashSet的put(Object o)方法时,首先会根据o.hashCode()的返回值定位到相应的bucket中,如果该bucket中没有结点,则将 o 放到这里,如果已经有结点了, 则把 o 挂到链表末端.同理,当调用contains(Object o)时,Java会通过hashCode()的返回值定位到相应的bucket中,然后再在对应的链表中的结点依次调用equals()方法来判断结点中的对象是否是你想要的对象.
下面我们通过一个例子来体会一下这个过程:
我们先创建2个新的Coder对象:
Coder c1 = new Coder("bruce", 10);
Coder c2 = new Coder("bruce", 10);
假定我们已经重写了Coder的equals()方法而没有重写hashCode()方法:
@Override
public boolean equals(Object other) {
System.out.println("equals method invoked!");
if(other == this)
return true;
if(!(other instanceof Coder))
return false;
Coder o = (Coder)other;
return o.name.equals(name) && o.age == age;
}
然后我们构造一个HashSet,将c1对象放入到set中:
Set<Coder> set = new HashSet<Coder>();
set.add(c1);
再执行:
System.out.println(set.contains(c2));
我们期望contains(c2)方法返回true, 但实际上它返回了false.
c1和c2的name和age都是相同的,为什么我把c1放到HashSet中后,再调用contains(c2)却返回false呢?这就是hashCode()在作怪了.因为你没有重写hashCode()方法,所以HashSet在查找c2时,会在不同的bucket中查找.比如c1放到05这个bucket中了,在查找c2时却在06这个bucket中找,这样当然找不到了.因此,我们重写hashCode()的目的在于,在A.equals(B)返回true的情况下,A,
B 的hashCode()要返回相同的值.
我让hashCode()每次都返回一个固定的数行吗
有人可能会这样重写:
@Override
public int hashCode() {
return 10;
}
如果这样的话,HashMap, HashSet等集合类就失去了其 "哈希的意义".用<Effective Java>中的话来说就是,哈希表退化成了链表.如果hashCode()每次都返回相同的数,那么所有的对象都会被放到同一个bucket中,每次执行查找操作都会遍历链表,这样就完全失去了哈希的作用.所以我们最好还是提供一个健壮的hashCode()为妙.
分享到:
相关推荐
主要介绍了重写hashCode()和equals()方法详细介绍,涉及重写equals()方法,重写hashCode()方法,重写equals()而不重写hashCode()的风险等相关内容的介绍,具有一定借鉴价值,需要的朋友可以参考下
重写equals和hashcode方法,学习和进步
Java重写equals同时需要重写hashCode的代码说明,以及如何重写hashCode方法,此代码演示按照effective java书籍说明的重写思路。代码中演示了使用集合存储对象,并且对象作为key,需重写equals和hashCode.
4、重写equals()方法5、为什么要重写hashCode()方法?6、什么时候需要重写hashCode()方法?7、重写hashCode()方法: 1、hashCode与equals两者之间的关系 如果两个对象相同(即用equals比较返回true),那么它们的...
NULL 博文链接:https://zpointer.iteye.com/blog/1058337
为什么重写equals方法,还必须要重写hashcode方法
在这篇文章中,我将告诉大家我对hashCode和equals方法的理解。我将讨论他们的默认实现,以及如何正确的重写他们。我也将使用Apache Commons提供的工具包做一个实现。 hashCode()和equals()定义在Object类中,这...
java 序列化和重写 hashCode 以及 equals 方法的例子
面试官瞬间就饱了,重写equals函数,需要重写hashCode函数吗? 面试官问我,为什么重写equals函数,必须重写hashCode函数,我当时就懵住了。 然后扯天扯地,然后面试官瞬间就饱了,痛定思痛,写下这篇博客 首先看...
关于重写equals,hashcode以及compareTo方法!
下面小编就为大家带来一篇java中重写equals()方法的同时要重写hashcode()方法(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
主要给大家介绍了关于java中为什么重写equals时必须重写hashCode方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
Object 类是所有类的父类,其 equals 方法比较的是两个对象的引用指向的地址,hashcode 是一个本地方法,返回的是对象地址值。他们都是通过比较地址来比较对象是否相等的
首先我们先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法
equals():反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值。 hashCode():计算出对象实例的...当然,当对象所对应的类重写了hashCode()方法时,结果就截然不同了。
主要介绍了java中重写equals和重写hashCode()的相关资料,需要的朋友可以参考下
主要介绍了Java重写equals及hashcode方法流程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
主要介绍了如何在IDEA中对 hashCode()和 equals() 利用快捷键快速进行方法重写,需要的朋友可以参考下
ATM系统 Clone方法 toString方法:表示返回对象的字符串表示形式 包名.类名@hashCode(内存地址) 打印对象时,默认调用 如果不是想使用object类,toString...如果equal返回true的话,hashCode相同,重写hashCode方法
但是为什么JavaDoc明确的告诉我们, hashCode()和equals()要一起重写呢?原因是因为,在Java自带的容器HashMap和HashSet中, 都需同时要用到对象的hashCode()和equals()方法来进行判断,然后再插入删除元素,这点...