重写object中的hashcode和equals方法

时间:2021-12-15 16:20:06

覆盖equals方法

1.      Object中对equals、hashcode方法的实现

(1)    在object中直接用   ==比较引用是不是指向同一个对象

(2)    直接返回对象在内存中的地址

 

2.      什么情况下不用覆盖equals方法

(1)    一个类的实例在本质上都是不同的。比如说Thread对象,每个Thread对象都是不同的。

(2)    不用去关心一个类的两个对象是不是逻辑上是相等的。

(3)    超类中已经覆盖了equals方法,而在子类中从超类继承过来的equals方法对于子类来说也是适用的。

(4)    私有类或者package类,这些类能保证外部不会调用equals方法,所以不需要去重写。

 

3.      什么情况下需要覆盖equals方法

(1)    逻辑上需要比较相等的类的对象,比如说double、float、PhoneNumber等。

这些类,我们归结为需要在程序中比较逻辑上相等性的value类。

(2)    需要作为key键值放入HashMap,HashSet,HashTable中的类的对象。因为这Hash集合中需要比较类的相等性。这时候不仅需要覆盖equals方法,还需要覆盖HashCode方法

 

4.      为什么要覆盖equals方法

(1)    逻辑上必须要比较类对象的相等性。

(2)    需要把对象放在Hash集合中。

 

5.      覆盖equals方法需要遵循的原则

5.1      覆盖equals后,对象必须要满足的特性

(1)    自反性

(2)    对称性

x.equals(y)为true,那么y.equals(x)也一定是true

(3)    传递性

x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也要true

(4)    一致性

(5)    对于null来说,x.equals(null)一定要false       

5.2      对称性

package equal.and.hashcode;

 

import java.util.ArrayList;

import java.util.List;

 

public class CaseInsensitiveString {

         private final Strings;

 

         publicCaseInsensitiveString(String s) {

                   if (s ==null)

                            thrownew NullPointerException();

                   this.s = s;

         }

 

         // Broken - violatessymmetry!

         @Override

         /**

          * 违法对称性

          * 如果x.equals(y) ==true;那么y.equals(x)也要true

          */

         public booleanequals(Object o) {

                   if (oinstanceof CaseInsensitiveString)

                            returns.equalsIgnoreCase(((CaseInsensitiveString) o).s);

                   if (oinstanceof String) // One-way interoperability!

                            returns.equalsIgnoreCase((String) o);

                   returnfalse;

         }

        

         public static voidmain(String[] args){

                   CaseInsensitiveStringcis = new CaseInsensitiveString("Hua");

                   String s ="hua";

                   System.out.println(s.equals(cis));

                   //为什么是true,因为CaseInsensitiveStringequals方法直接用内部域

                   String s来判断;而在String 类中,根本不知道有CaseInsensitiveString

                   System.out.println(cis.equals(s));

         }

}

 

修正:

public boolean equals(Object o) {

       if(!(oinstanceofCaseInsensitiveString))

           return false;

       CaseInsensitiveStringother = (CaseInsensitiveString) o;

       return other.s ==s;

}

5.3      传递性

(1)    第一种情况,子类不覆盖父类的equals方法

这种情况下,相当于子类中新添加的域z不在比较。只要他们位置x,y是相同的,就表示逻辑上是相同的。

父类:

packageequal.and.hashcode;

 

public class Point {

  private int x;

  private int y;

 

  public Point(int x,int y){

     this.x = x;

     this.y = y;

  }

 

  @Override

  public boolean equals(Object obj) {

     if(!(objinstanceof Point))

         returnfalse;

     Point p =(Point) obj;

     return p.x ==x && p.y ==y;

  }

 

 

 

}

子类:

packageequal.and.hashcode;

public class ColorPoint extends Point{

    private int z;

   

   

    public ColorPoint(int x,int y,int z) {

       super(x, y);

       this.z = z;

    }

 

    public static void main(String[]args){

       ColorPoint c1 = new ColorPoint(1,1,2);

       Point p = new Point(1,1);

       ColorPoint c2 = new ColorPoint(1,1,6);

       //违反对称性

       System.out.println(c1.equals(p));

       System.out.println(p.equals(c2));

      

       System.out.println(c1.equals(c2));

    }

 

}

 

(2)    第一种情况,子类覆盖equals方法

情况1:

public boolean equals(Object obj) {

       if(!(objinstanceof ColorPoint ))

           return false;

       return super.equals(obj) && ((ColorPoint) obj).z ==z;

}

       比较:

       ColorPoint c1 = new ColorPoint(1,1,2);

       Point p = new Point(1,1);

//违反对称性

       System.out.println(c1.equals(p)); --调用子类的equals,返回false

       System.out.println(p.equals(c2)); --调用父类的equals,返回true

 

       情况2:

        @Override

    public boolean equals(Object obj){

       //obj不是Point的对象

       if(!(objinstanceof Point))//1

           returnfalse;

       //obj只是Point的对象

       if(!(objinstanceof ColorPoint))//2

           returnsuper.equals(obj);

        //objColorPoint的对象

       returnsuper.equals(obj)&& ((ColorPoint) obj).z ==z;//3

    }

        用equals比较:

       ColorPoint c1 = new ColorPoint(1,1,2);

       Point p = new Point(1,1);

       ColorPoint c2 = new ColorPoint(1,1,6);

       //违反了传递性

       System.out.println(c1.equals(p));--调用子类的equals,走到2

       System.out.println(p.equals(c2));--调用父类的equals

      

       System.out.println(c1.equals(c2));--调用子类的方法,走到3

    最后,发现违法了传递性

    继承一个父类时,既想在子类中添加新的域,又想保证equals约定,实现上很困难的。

    修正:

    利用组合能很容易的修正:

    package equal.and.hashcode;

 

import java.util.HashMap;

import java.util.Map;

 

public class ColorPoint2 {

   

    private Point point;

    private int z;

   

    public ColorPoint2(Point p,intz){

       this.point = p;

       this.z = z;

    }

 

 

    @Override

    public boolean equals(Object obj){

       if(!(obj instanceofColorPoint2))

           return false;

       ColorPoint2 other =(ColorPoint2) obj;

       returnother.point.equals(point) && other.z == z;

    }

 

 

 

    public static void main(String[]args){

       ColorPoint2 c1 = newColorPoint2(new Point(1,1),3);

       ColorPoint2 c2 = newColorPoint2(new Point(1,1),3);

       

       ColorPoint2 c3 = newColorPoint2(new Point(1,1),3);

      

       System.out.println(c1.equals(c2));

       System.out.println(c2.equals(c3));

       System.out.println(c1.equals(c3));

      

      

    }

 

}

 

6.      覆盖equals的步骤:

(1)    用==判断是不是指向同一个对象

(2)    用instanceof判断参数是不是正确的类型

(3)    强制转换成正确的类型

(4)    用逻辑上判断相等的关键域来判断相等。

Float用floatToIntBit转换成int在比较

Double用doubleToLongBit转换成long在用==比较

对于引用要把关键域一一做比较

对于数组要把数组中的每个元素做比较

对于Null的处理

(field == o.field || (field != null &&field.equals(o.field)))

         覆盖完equals方法后,一定要覆盖hashcode方法

 

 

                                                                 Hashcode覆盖

1.      Hashcode的作用

1.1   hashCode()是用来产生哈希玛的,而哈希玛是用来在散列存储结构中确定对象的存储地址的。

1.2   Object中默认的equalsHashCode实现

(1)    默认情况下equals比较引用是不是指向同一个对象

(2)    默认情况下HashCode返回对象内存地址的整数值

对象需要比较逻辑上相等的话,需要重写equals和HashCode方法。

重写hashcode是为了产生哈希玛,而哈希玛是用来在散列存储结构中确定对象的存储地址的。比如hashSet,hashMap这类结构。

如果两个对象hashcode相等,那么需要比较equals

1.3   HashSet中的hashCodeequals

在将对象存入HashSet中时,通过被存入对象的 hashCode()来确定对象在 HashSet中的存储地址,通过equals()来确定存入的对象是否重复。如果不去重写equalshashcode方法的话,即使两个对象内容相同,但是hashcode一定不同,equals比较这两个对象一定不同。也就说,在HashSet中保存了逻辑上相同的两个对象。

所以需要存入HashSet并且需要比较逻辑上相等的两个对象需要同时重写equalshashcode方法。

需满足以下条件:

Equals比较两个对象相等的,他们的hashcode也相同。

存:

满足这个条件在向HashSet中存放对象的时候,根据对象的hashcode定位到应该把这个对象放在HastSet的那个位置,如果这个位置没有对象,直接存放。如果有对象需要用equals比较。

取:

hashset中取对象的时候,直接根据hashcode就能定位到HashSet保存这个对象的位置,不需要每次都去一一用equals方法比较对象是不是相等了。

 

Java中对HashSet是用HashMap来实现的。

1.4   HashMap中的hashCodeequals

HashMap中根据keyhashCode来保存value

hashCode()方法使用来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的桶里面,Map在搜索一个对象的时候先通过 hashCode()找到相应的桶,然后再根据equals()方法找到相应的对象.要正确的实现Map里面查找元素必须满足一下两个条件
(1)
obj1.equals(obj2)trueobj1.hashCode()== obj2.hashCode()必须为true 
(2)
obj1.hashCode() == obj2.hashCode()falseobj.equals(obj2)必须为false

 

2.      什么情况下需要覆盖hashcode

逻辑上需要比较相等性的类,需要实现。

 

3.      覆盖hashcode的原则

原则:1

Equals比较相等的两个对象,hashcode要相同。

举个例子来说明:

public final class PhoneNumber {

    private final int areaCode;

    private final int exchange;

    private final int extension;

   

    public PhoneNumber(int a,int exc,int ext) {

       this.areaCode = a;

       this.exchange = exc;

       this.extension = ext;

    }

 

    @Override

    public boolean equals(Object obj) {

       if(obj ==this)

           return true;

       if(!(objinstanceof PhoneNumber))

           return false;

       PhoneNumber p = (PhoneNumber) obj;

       return p.areaCode ==areaCode && p.exchange ==exchange && extension == p.extension;

      

}

PhoneNumber类中重写了equals方法,但是没有去重写hashcode方法,导致在HashMap中行为不确定。如下

PhoneNumber p = new PhoneNumber(1,2,3);

       Map<PhoneNumber,String> map = newHashMap<PhoneNumber, String>();

       map.put(p, "hua");

System.out.println(map.get(new PhoneNumber(1,2,3)));--取出的是null

这是因为PhoneNumber类没有重写hashcode方法,即使两个对象在逻辑上是相等的,但是没有重写hashcode方法,导致这两个对象的hashcode不同,然后hashmap根据key的hashcode去查找value,当然找不到对应的value了。

 

如果这么实现hashcode行吗?

Public int hashCode(){

    Return 42;

}

答案是不行,因为这样的话,保存到散列表中后,所有对象都保存在同一个桶中,hashMap退化为链表,性能从线下变成o2了。

所以实现hashcode,要尽量保证不相等的对象又不同的hashcode

 

原则2:

逻辑上不相等的对象的hashcode尽量要不同。如果相同的话,保存到散列表后,散列表的性能会下降。

在某个桶中,先根据对象的hashcode找到对象在散列表中的index位置;如果散列表的这个位置只有一个对象,那么直接返回;如果有两个对象以上对象,那么需要在这个位置的链表结构中用对象的equals方法一个一个比较对象。

 

4.  覆盖hashcode的步骤

不多说,直接看effective java。