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

来源:互联网 发布:mac如何显示隐藏文件 编辑:程序博客网 时间:2024/05/22 04:26

站稳马步

——整理Object类(为什么重写equalshashCode方法)

一.            关键字:

Objectequals()hashCode()

二.            为什么需要重写:

众所周知,Object是所有类的父类。但,我们在实际开发中自定义自己类时,往往需要重写ObjectequalshashCode方法。为什么呢?首先看看ObjectAPI吧。

Object类中原始写法是:

public boolean equals(Object obj) {

              return (this == obj);

 }

可见,原始equals比较的是2个对象的“内存地址”。但,我们往往是需要判断的是“逻辑上的内容”是否相等,如:StringIntegerMath...等等,时而我们关心是逻辑内容上是否相等,而不关心是否指向同一对象,所以所要重写。

再者,尽管Object是一个具体的类,但是设计它主要是为了扩展。它所要的非final方法(equals hashCode toString clonefinalize)都有通用约定(general contract),因为它们被设计成要被覆盖(override)的。任何一个类,它在覆盖这些方法的时候,都有责任遵守这些通用的约定;如果不能做到这一点,其它依赖这些约定的类(例如HashMapHashSet)就无法结合该类一起正常运行。

       JDKAPI上重写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) {//HashSetadd方法

           return map.put(o, PRESENT)==null;

    }

 

public Object put(Object key, Objectvalue) {//HashMapput方法

        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;

    }

//没有重写equalshashCode

}

 

/**

 * @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]对于非floatdouble类型的原语类型域,使用==比较;

[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.      http://zhangjunhd.blog.51cto.com/113473/71571

 

原创粉丝点击