hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。
?
使用hashCode()和equals()
hashCode()方法被用来获取给定对象的唯一整数。这个整数被用来确定对象被存储在HashTable类似的结构中的位置。默认的,Object类的hashCode()方法返回这个对象存储的内存地址的编号。
重写默认的实现
如果你不重写这两个方法,将几乎不遇到任何问题,但是有的时候程序要求我们必须改变一些对象的默认实现。
来看看这个例子,让我们创建一个简单的类Employee
?
monospace !important; float: none !important; height: auto !important; font-size: 10pt !important; vertical-align: baseline !important; font-weight: normal !important; padding-top: 0px; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; border: #dddddd 1px solid;">01
class="keyword" style="background-image: none !important; text-align: left !important; padding-bottom: 0px; line-height: 1.1em !important; background-color: #f6f6f6; font-style: normal !important; margin: 0px 2px; padding-left: 5px; width: auto !important; padding-right: 5px; font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace !important; float: none !important; height: auto !important; color: #006699 !important; font-size: 10pt !important; vertical-align: baseline !important; font-weight: bold !important; padding-top: 0px; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; border: #dddddd 1px solid;">public
?class
?Employee
02
{
03
????
private
?Integer id;
04
????
private
?String firstname;
05
????
private
?String lastName;
06
????
private
?String department;
07
?
08
????
public
?Integer getId() {
09
????????
return
?id;
10
????
}
11
????
public
?void
?setId(Integer id) {
12
????????
this
.id = id;
13
????
}
14
????
public
?String getFirstname() {
15
????????
return
?firstname;
16
????
}
17
????
public
?void
?setFirstname(String firstname) {
18
????????
this
.firstname = firstname;
19
????
}
20
????
public
?String getLastName() {
21
????????
return
?lastName;
22
????
}
23
????
public
?void
?setLastName(String lastName) {
24
????????
this
.lastName = lastName;
25
????
}
26
????
public
?String getDepartment() {
27
????????
return
?department;
28
????
}
29
????
public
?void
?setDepartment(String department) {
30
????????
this
.department = department;
31
????
}
32
}
上面的Employee类只是有一些非常基础的属性和getter、setter.现在来考虑一个你需要比较两个employee的情形。
?
?
01
public
?class
?EqualsTest {
02
????
public
?static
?void
?main(String[] args) {
03
????????
Employee e1 =?
new
?Employee();
04
????????
Employee e2 =?
new
?Employee();
05
?
06
????????
e1.setId(
100
);
07
????????
e2.setId(
100
);
08
????????
//Prints false in console
09
????????
System.out.println(e1.equals(e2));
10
????
}
11
}
毫无疑问,上面的程序将输出false,但是,事实上上面两个对象代表的是通过一个employee。真正的商业逻辑希望我们返回true。?
为了达到这个目的,我们需要重写equals方法。?
01
public
?boolean
?equals(Object o) {
02
????????
if
(o ==?
null
)
03
????????
{
04
????????????
return
?false
;
05
????????
}
06
????????
if
?(o ==?
this
)
07
????????
{
08
???????????
return
?true
;
09
????????
}
10
????????
if
?(getClass() != o.getClass())
11
????????
{
12
????????????
return
?false
;
13
????????
}
14
????????
Employee e = (Employee) o;
15
????????
return
?(
this
.getId() == e.getId());
16
}
在上面的类中添加这个方法,EauqlsTest将会输出true。?
So are we done?没有,让我们换一种测试方法来看看。?
01
import
?java.util.HashSet;
02
import
?java.util.Set;
03
?
04
public
?class
?EqualsTest
05
{
06
????
public
?static
?void
?main(String[] args)
07
????
{
08
????????
Employee e1 =?
new
?Employee();
09
????????
Employee e2 =?
new
?Employee();
10
?
11
????????
e1.setId(
100
);
12
????????
e2.setId(
100
);
13
?
14
????????
//Prints 'true'
15
????????
System.out.println(e1.equals(e2));
16
?
17
????????
Set<Employee> employees =?
new
?HashSet<Employee>();
18
????????
employees.add(e1);
19
????????
employees.add(e2);
20
????????
//Prints two objects
21
????????
System.out.println(employees);
22
????
}
上面的程序输出的结果是两个。如果两个employee对象equals返回true,Set中应该只存储一个对象才对,问题在哪里呢??
我们忘掉了第二个重要的方法hashCode()。就像JDK的Javadoc中所说的一样,如果重写equals()方法必须要重写hashCode()方法。我们加上下面这个方法,程序将执行正确。
1
@Override
2
?
public
?int
?hashCode()
3
?
{
4
????
final
?int
?PRIME =?
31
;
5
????
int
?result =?
1
;
6
????
result = PRIME * result + getId();
7
????
return
?result;
8
?
}
使用Apache Commons Lang包重写hashCode() 和equals()方法?
Apache Commons 包提供了两个非常优秀的类来生成hashCode()和equals()方法。看下面的程序。
?
view source print?01
import
?org.apache.commons.lang3.builder.EqualsBuilder;
02
import
?org.apache.commons.lang3.builder.HashCodeBuilder;
03
public
?class
?Employee
04
{
05
?
private
?Integer id;
06
?
private
?String firstname;
07
?
private
?String lastName;
08
?
private
?String department;
09
public
?Integer getId() {
10
????
return
?id;
11
?
}
12
?
public
?void
?setId(Integer id) {
13
????
this
.id = id;
14
?
}
15
?
public
?String getFirstname() {
16
????
return
?firstname;
17
?
}
18
?
public
?void
?setFirstname(String firstname) {
19
????
this
.firstname = firstname;
20
?
}
21
?
public
?String getLastName() {
22
????
return
?lastName;
23
?
}
24
?
public
?void
?setLastName(String lastName) {
25
????
this
.lastName = lastName;
26
?
}
27
?
public
?String getDepartment() {
28
????
return
?department;
29
?
}
30
?
public
?void
?setDepartment(String department) {
31
????
this
.department = department;
32
?
}
33
@Override
34
?
public
?int
?hashCode()
35
?
{
36
????
final
?int
?PRIME =?
31
;
37
????
return
?new
?HashCodeBuilder(getId()%
2
==
0
?getId()+
1
:getId(), PRIME).
38
???????????
toHashCode();
39
?
}
40
@Override
41
?
public
?boolean
?equals(Object o) {
42
????
if
?(o ==?
null
)
43
???????
return
?false
;
44
????
if
?(o ==?
this
)
45
???????
return
?true
;
46
????
if
?(o.getClass() != getClass())
47
???????
return
?false
;
48
????
Employee e = (Employee) o;
49
???????
return
?new
?EqualsBuilder().
50
??????????????
append(getId(), e.getId()).
51
??????????????
isEquals();
52
????
}
53
?
}
如果你使用Eclipse或者其他的IDE,IDE也可能会提供生成良好的hashCode()方法和equals()方法。?
需要注意记住的事情
当使用ORM的时候特别要注意的
public int hashCode() {?
? ??int h = hash;?
? int len = count;?
? if (h == 0 && len > 0) {?
? int off = offset;?
? char val[] = value;?
? for (int i = 0; i < len; i++) {?
? ? ?h = 31*h + val[off++];?
? ?}?
? hash = h;?
? }?
? return h;?
}?
该函数是我看的函数接口源码,为什么要使用31这个数呢?
?
其实上面的实现也可以总结成数数里面下面这样的公式:
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
A.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)
B.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
C.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
D.并且31只占用5bits,相乘造成数据溢出的概率较小。