站稳马步——(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
——整理
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
相关文章推荐
- 正确重写equals()和hashCode()方法
- 容器 第二节 重写equals和hashCode方法
- 正确重写hashcode hashcode与equals方法 集合元素如何判断是否相等 集合如何查看是否包含某个元素
- Hibernate联合主键重写equals和hashCode方法
- Hashtable记录,重写equals和 hashcode方法
- java为什么要重写hashCode和equals方法
- HashSet中存方用户自定义数据类型数据,重写equals方法和hashCode方法
- 实体类自动重写toString(),equals()和hashCode()方法
- JAVA中重写equals()方法的同时要重写hashcode()方法
- 重写equals() 和 hashCode()方法
- 关于重写equals,hashcode以及compareTo方法
- 新建的类对象,在重写equals方法后为什么要重写hashcode方法?
- 重写equals()方法就必须重写hashCode()方法的原因
- JAVA中重写equals()方法的同时要重写hashcode()方法
- 重写equals方法和hashcode方法的作用
- 为什么重写equals方法必须重写hashCode?
- 为什么重写equals方法,一定要重写HashCode方法?(
- hibernate实体类中为何要重写equals与hashcode方法
- HashSet中equals()与hashCode()方法的重写
- JAVA重写equals方法以及hashCode方法