您的位置:首页 > 其它

equals 和 hashCode 的重写方法

2015-08-11 09:58 148 查看
http://blog.sina.com.cn/s/blog_7355a0bf0100ps4f.html

为什么需要重写:

众所周知, 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

}

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