为啥重写 equals 和 hashCode 方法(一)

为什么重写 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 约定如下:

自反性

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

对称性

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

传递性

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

一致性

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

对于任何非空引用值 xx.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.

 ---->继续第二页