您的位置:首页 > 其它

站稳马步——(1)重写 equals 和 hashCode 方法

2010-10-25 09:39 525 查看
站稳马步


——整理
Object
类(为什么重写
equals

hashCode
方法)

一.


关键字:


Object

equals()

hashCode
()

二.


为什么需要重写:


众所周知,
Object
是所有类的父类。但,我们在实际开发中自定义自己类时,往往需要重写
Object

equals

hashCode
方法。为什么呢?首先看看
Object

API
吧。

Object
类中原始写法是:

public


boolean


equals

(

Object

obj

)


{


return


(

this

== obj

)

;

}


可见,原始

equals

比较的是

2

个对象的“内存地址”。但,我们往往是需要判断的是“逻辑上的内容”是否相等,如:

String



Integer



Math...

等等,时而我们关心是逻辑内容上是否相等,而不关心是否指向同一对象,所以所要重写。


再者,尽管

Object

是一个具体的类,但是设计它主要是为了扩展。它所要的非

final

方法(

equals hashCode toString clone



finalize

)都有通用约定(

general contract

),因为它们被设计成要被覆盖(

override

)的。任何一个类,它在覆盖这些方法的时候,都有责任遵守这些通用的约定;如果不能做到这一点,其它依赖这些约定的类(例如

HashMap



HashSet

)就无法结合该类一起正常运行。


JDK
API
上重写
equals
约定如下:

自反性


对于任何非空引用值 x
,x.equals(x)
都应返回 true


对称性


对于任何非空引用值 x
和 y
,当且仅当
y.equals(x)
返回 true
时,x.equals(y)

才应返回 true


传递性


对于任何非空引用值 x
、y
和 z
,如果 x.equals(y)
返回 true
,并且 y.equals(z)
返回 true
,那么 x.equals(z)
应返回 true


一致性


对于任何非空引用值 x
和 y
,多次调用
x.equals(y)
始终返回 true
或始终返回
false
,前提是对象上 equals
比较中所用的信息没有被修改。

对于任何非空引用值 x
,x.equals(null)
都应返回 false

同时,

API

规定“

当此方法被重写时,通常有必要重写
hashCode

方法,以维护
hashCode

方法的常规协定,该协定声明相等对象必须具有相等的哈希码
“所以也要重写

hashCode

方法。


public


native


int


hashCode

()

;

说明是一个本地方法,它的实现是根据本地机器相关的,方法返回的是对象的地址值。时而重写
hashCode
一是为了遵守
API
约定,二是重点提高对象比较时效率。

因为,在
java
集合对象中比较对象是这样的,如
HashSet
中是不可以放入重复对象的,那么在
HashSet
中又是怎样判定元素是否重复的呢?让我们看看源代码(首先要知道
HashSet
内部实际是通过
HashMap
封装的):

public


boolean

add
(

Object o
)


{

//HashSet

add
方法

return

map.put
(

o, PRESENT
)

==
null

;

}


public

Object put
(

Object key, Object
value
)


{

//HashMap

put
方法

Object k =
maskNull
(

key
)

;

int

hash
= hash
(

k
)

;

int

i = indexFor
(

hash
, table.length
)

;

for


(

Entry e = table
[

i
]

; e !=
null

; e = e.next
)


{


if


(

e.hash == hash
&& eq
(

k, e.key
))


{

//

从这里可见先比较

hashcode


Object oldValue = e.value;

e.value = value;

e.recordAccess
(

this

)

;

return

oldValue;

}


}


modCount++;

addEntry
(

hash
, k, value, i
)

;

return


null

;

}


所以在
java

的集合中,判断两个对象是否相等的规则是:


1
,判断两个对象的
hashCode
是否相等

如果不相等,认为两个对象也不相等,完毕

如果相等,转入
2

2
,判断两个对象用
equals
运算是否相等

如果不相等,认为两个对象也不相等

如果相等,认为两个对象相等

为什么是两条准则,难道用第一条不行吗?不行,因为
hashCode()
相等时,
equals()
方法也可能不等
,所以必须用第
2
条准则进行限制,才能保证加入的为非重复元素。

三.


例子:


1.
首先

class


Student

{


String


name
;

int


age
;

Student

(

String

name
,
int

age

){


this

.
name
=name
;

this

.
age
=age
;

}


//

没有重写

equals



hashCode


}


/**

*

@author


ydj

*

@version


Apr

28,

2010

3:12:42

PM

*/

public


class


OverEqualsHashcodeTest


{


public


static


void


main

(

String


[]

args

){


Set


<
Student

> set=
new


HashSet

<
Student

>
()

;

Student

stu1=
new


Student

(

"ydj"
,26
)

;

Student

stu2=
new


Student

(

"ydj"
,26
)

;

set.add

(

stu1
)

;

set.add

(

stu2
)

;

System

.out

.
println

(

"set.size():"
+set.size

())

;

}


}


结果是:

2.

(这个无须解释)


2.
现在重写
equals
方法如下:

public


boolean


equals

(

Object

obj

){


System

.out

.
println

(

"--------equals()-----------:"
+obj

)

;

if

(

obj

==
null

){


return


false

;

}


if

(

!
(

obj

instanceof


Student

)){


return


false

;

}

else


{


Student

oth=
(

Student

)

obj

;

return


this

.
age
==oth.
age
&&
this

.
name
==oth.
name
;

}


//
return true;

}


结果是:

2.

(为什么依然是

2

呢?!为什么连

equals

方法都没调用呢)


分析:

这就是为什么要重写
hashCode
的原因(
相等对象必须具有相等的哈希码
)。因为现在的
hashCode
依然返回各自对象的地址,就是说明此时的
hashCode
肯定不相等,故根本不会调用
equals
()。

3.
重写
hashCode
方法如下:

public


int


hashCode

(){


int

res=17;

res=31*res+
age
;

res=31*res+
name
.
hashCode

()

;

return

res;

}


结果是:

1.


如果这样重写
hashCode


public


int


hashCode

(){


int

res=
(

int

)(

Math.random

()

*100
)

;

return

res;

}


这样的话,就等于没有重写了。

四.


设计
equals

()和
hashCode

():


A
.设计equals()

[1]
使用instanceof
操作符检查“实参是否为正确的类型”。

[2]
对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。

[2.1]
对于非float
和double
类型的原语类型域,使用==
比较;

[2.2]
对于对象引用域,递归调用equals
方法;

[2.3]
对于float
域,使用
Float.floatToIntBits
(
afloat
)
转换为int
,再使用==
比较;

[2.4]
对于double
域,使用
Double.doubleToLongBits
(
adouble
)

转换为int
,再使用==
比较;

[2.5]
对于数组域,调用Arrays.equals
方法。

B.
设计hashCode()

[1]
把某个非零常数值,例如17
,保存在int
变量result
中;

[2]
对于对象中每一个关键域f
(指equals
方法中考虑的每一个域):

[2.1]boolean
型,计算(f ? 0 : 1);

[2.2]byte,char,short
型,计算(int);

[2.3]long
型,计算(int) (f ^ (f>>>32));

[2.4]float
型,计算
Float.floatToIntBits
(
afloat
)
;

[2.5]double
型,计算
Double.doubleToLongBits
(
adouble
)
得到一个long
,再执行[2.3];

[2.6]
对象引用,递归调用它的hashCode
方法;

[2.7]
数组域,对其中每个元素调用它的hashCode
方法。

[3]
将上面计算得到的散列码保存到int
变量c
,然后执行 result=37*result+c;

[4]
返回result


例子:

class


Unit


{


private


short


ashort
;

private


char


achar
;

private


byte


abyte
;

private


boolean


abool
;

private


long


along
;

private


float


afloat
;

private


double


adouble
;

private


Unit


aObject
;

private


int

[]


ints
;

private


Unit

[]


units
;

public


boolean


equals

(

Object

o

)


{


if


(

!
(

o

instanceof


Unit

))


return


false

;

Unit

unit =
(

Unit

)

o
;

return

unit.
ashort
==
ashort

&& unit.
achar
==
achar

&& unit.
abyte
==
abyte

&& unit.
abool
==
abool

&& unit.
along
==
along

&& Float.floatToIntBits

(

unit.
afloat
)

== Float

.floatToIntBits

(

afloat
)


&& Double.doubleToLongBits

(

unit.
adouble
)

== Double

.doubleToLongBits

(

adouble
)


&& unit.
aObject
.
equals

(

aObject
)

&&
equalsInts

(

unit.
ints
)


&&
equalsUnits

(

unit.
units
)

;

}


private


boolean


equalsInts

(

int

[]

aints

)


{


return

Arrays.equals

(

ints
, aints

)

;

}


private


boolean


equalsUnits

(

Unit

[]

aUnits

)


{


return

Arrays.equals

(

units
, aUnits

)

;

}


public


int


hashCode

()


{


int

result = 17;

result = 31 * result +
(

int

)


ashort
;

result = 31 * result +
(

int

)


achar
;

result = 31 * result +
(

int

)


abyte
;

result = 31 * result +
(

abool
? 0 : 1
)

;

result = 31 * result +
(

int

)


(

along
^
(

along
>>> 32
))

;

result = 31 * result + Float.floatToIntBits

(

afloat
)

;

long

tolong = Double.doubleToLongBits

(

adouble
)

;

result = 31 * result +
(

int

)


(

tolong ^
(

tolong >>> 32
))

;

result = 31 * result +
aObject
.
hashCode

()

;

result = 31 * result +
intsHashCode

(

ints
)

;

result = 31 * result +
unitsHashCode

(

units
)

;

return

result;

}


private


int


intsHashCode

(

int

[]

aints

)


{


int

result = 17;

for


(

int

i = 0; i < aints
.
length
; i++
)


result = 31 * result + aints

[

i
]

;

return

result;

}


private


int


unitsHashCode

(

Unit

[]

aUnits

)


{


int

result = 17;

for


(

int

i = 0; i < aUnits
.
length
; i++
)


result = 31 * result + aUnits

[

i
]

.
hashCode

()

;

return

result;

}


}


为什么要用
31
这个数呢?因为它是个奇素数。如果乘以偶数,并且乘法溢出的话,信息就会丢失,因为与
2
相乘等价于移位运算。使用素数效果不是很明显,但是习惯上都是使用素数计算散列结果。
31
有个好处的特性,即用移位代替乘法,可以得到更好的性能:
31*I = =

I << 5

- I
。现代的
VM
可以自动完成这样优化。
——《
Effctive java SE


五.


注意:


1.equals
()不相等的两个对象,却并不能证明他们的hashcode()
不相等。

换句话说,equals()
方法不相等的两个对象,hashcode()
有可能相等

2.hashcode()
不等,一定能推出equals()
也不等;hashcode()
相等,equals()
可能相等,也可能不等。

六.


参考:


1.


http://www.cnjm.net/tech/article4731.html



2.


/article/4381737.html

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: