hashcode()和equals()及HashSet判断对象相等

时间:2021-11-03 16:18:27
hashcode()和equals()及HashSet判断对象相等。
1. 首先equals()和hashcode()这两个方法都是从object类中继承过来的。
public boolean equals(Object obj)
    指示某个其他对象是否与此对象“相等”。
     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。 
    注意:以上这五点是重写equals()方法时, 必须遵守的准则,如果违反会出现意想不到的结果
    Object 类的 equals 方法实现的是对象上差别可能性最大的相等关系:
    即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,
    此方法才返回 true(x == y 具有值 true)。
  也就是说equals()方法在object类中定义如下:
    public boolean equals(Object obj) {
  return (this == obj);
  }

   对两个对象的地址值进行的比较(即比较引用是否相同)。
    注意1:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,
    该协定声明 相等对象必须具有相等的哈希码
 但是我们必需清楚,当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,
 已经覆盖了object类的equals()方法。比如在String类中如下:
 public boolean  equals (Object anObject) {
  
if  (this == anObject) {
   
return  true;
  }
  
if  (anObject  instanceof  String) {
    String anotherString = (String)anObject;
    int n = count;
    
if  (n == anotherString.count) {
    char v1[] = value;
    char v2[] = anotherString.value;
    int i = offset;
    int j = anotherString.offset;
    while (n-- != 0) {
    
 if  (v1[i++] != v2[j++])
      
return  false;
    }
    
return  true;
   }
  }
  
return  false;
 }

 很明显, 这是进行的内容比较,而已经不再是地址的比较 。依次类推Double、Integer、Math等等这些类都是重写了equals()方法的,
 从而进行的是内容的比较。当然了基本类型是进行值的比较,这个没有什么好说的。
public int hashCode()
     返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
     hashCode 的常规协定是:
        *  在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,
         前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,
         该整数无需保持一致。

        *  如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果
        * 以下情况 不是必需的: 如果根据 equals(java.lang.Object) 方法,两个对象不相等,
         那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果

          但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。 
    实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
    (这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 。
    当然我们可以在自己写的类中覆盖hashcode()方法,
    比如String、 Integer、Double。。。。等等这些类都是覆盖了hashcode()方法的。
    例如在String类中定义的hashcode()方法如下:
   public int   hashCode () {
   
int  h = hash;
   
if  (h == 0) {
    int off = offset;
    char val[] = value;
    
int  len = count;
        for (int i = 0; i < len; i++) {
            h = 31*h + val[off++];
        }
       hash = h;
   }
   
return  h;
  }

 解释一下这个程序(String的API中写到):
  s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 使用 int 算法,这里 s 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。)
 这里我们根据hashCode的常规协定首先要明白一个问题:
  一, equals()相等的两个对象,hashcode()一定相等.
  二, equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
   换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。
  三,反过来: hashcode()不等,一定能推出equals()也不等(因为如果equals()相等的话就和第一条矛盾了);
   hashcode()相等,equals()可能相等,也可能不等。
2.hashset是根据什么原理来存取对象的呢?
 在hashset中不允许出现重复对象,元素的位置也是不确定的。
 在hashset中判断 两个对象是否重复的规则是:
   1), 判断两个对象的hashCode是否相等. 
         如果不相等,认为两个对象也不相等,完毕;    如果相等,转入2)
  (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,
   所以我们这里将其做为必需的。后面会重点讲到这个问题。)
  2), 判断两个对象用equals运算是否相等
        如果不相等,认为两个对象也不相等;     如果相等,认为两个对象相等
(equals()是判断两个对象是否相等的关键)
 为什么是两条准则,难道用第一条不行吗? 不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,
 所以必须用第2条准则进行限制,才能保证加入的为非重复元素

 那么为什么不直接用equals()方法判断呢?
  我想是因为效率的原因。很容易让hashcode()的效率比equals()高些,
 因为它不必每次都重新算hash值,具体参照上面String的hashcode()实现

例1:
  public static void  main (String args[]){
 String s1=new String("zhaoxudong");
 String s2=new String("zhaoxudong");
 System.out.println(s1==s2);
//false
 System.out.println(s1.equals(s2)); //true
 System.out.println(s1.hashCode()); //s1.hashcode()等于s2.hashcode()
 System.out.println(s2.hashCode());
 Set hashset=new HashSet();
 hashset.add(s1);
 hashset.add(s2);
 Iterator it=hashset.iterator();
   while (it.hasNext())
  {
    System.out.println(it.next());
  }

最后在while循环的时候只打印出了一个”zhaoxudong”。
输出结果为:
false
true
-967303459
-967303459
zhaoxudong
这是因为String类已经重写了equals()方法和hashcode()方法,所以在根据上面的第1.2条原则判定时,
hashset认为它们是相等的对象,是重复添加。
例2
  import java.util.*;
 public class  HashSetTest
 {
  
public static void  main(String[] args)
  {
   HashSet hs=new HashSet();
   hs.add(new Student(1,"zhangsan"));
   hs.add(new Student(2,"lisi"));
   hs.add(new Student(3,"wangwu"));
   hs.add(new Student(1,"zhangsan"));
  
   Iterator it=hs.iterator();
   
while (it.hasNext())
   {
    System.out.println(it.next());
   }
  }
 }
 
class  Student
 {
  int num;
  String name;
  Student(int num,String name)
  {
   this.num=num;
   this.name=name;
  }
  public String toString()
  {
    return num+":"+name;
  }
 }     

输出结果为:
 1:zhangsan
 1:zhangsan
 3:wangwu
 2:lisi

问题出现了:为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有
 因为在根据hashcode()对两次建立的new Student(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,
 所以hashset把他当作不同的对象对待了,当然此时的 equals()方法返回的值也不等(这个不用解释了吧)。
 那么为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重写自己的hashcode()和equals()方法,
 所以在比较时,是继承的object类中的 hashcode()方法,它返回的是和对象地址(引用地址)对应的数,
 使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样。
 所以根据第一个准则,hashset会把它们当作不同的对象对待,自然也用不着第二个准则进行判定了。
那么怎么解决这个问题呢?
答案是:在Student类中重新hashcode()和equals()方法。
例3
  class   Student
 {
  int num;
  String name;
   Student (int num,String name)
  {
   this.num=num;
   this.name=name;
  }
   public int  hashCode()
  {
   return num*name.hashCode();
  }
   public boolean  equals(Object o)
  {
   Student s=(Student)o;
    return  num==s.num && name.equals(s.name);
  }
   public  String toString()
  {
    return  num+":"+name;
  }
 }
根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,获得的哈希码肯定是一样的。
当然根据equals()方法我们也可判断是相同的。所以在向hashset集合中添加时把它们当作重复元素看待了。
所以运行修改后的程序时,我们会发现运行结果是:
1:zhangsan
3:wangwu
2:lisi

可以看到重复元素的问题已经消除。
例4
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class  Test {
 /**
  * @param args
  */

  public static void   main (String[] args) {
  HashSet<People> set=new HashSet();
  set.add(new People("robin",1,21));
  set.add(new People("hb",2,20));
  set.add(new People("harry",3,30));
  set.add(null);
  People p4=new People("robin",4,25);
  set.add(p4);
  set.add(new People("yp",5,28));
  for(People p:set)
   System.out.println(p);
 }
}
class  People {
 String name;
 int id;
 int age;
  public  People(String name,int id)
 {
  this(name,id,0);
 }
  public  People(String name,int id,int age)
 {
  this.name=name;
  this.id=id;
  this.age=age;
 }
  public  String toString()
 {
  return id+name+age;
 }
  public boolean  equals(Object o)
 {
  if(o==null)
   return false;
  if(!(o instanceof People))
   return false;
  People p=(People)o;
  boolean res=name.equals(p.name);
  if(res)
   System.out.println("name "+name+" is double");
  else
   System.out.println(name+" vS "+p.name);
  return res;
 }
  public int  hashCode()
 {
   return  name.hashCode();
 }
}
结果:
name robin is double
null
1robin21
3harry30
5yp28
2hb20

注意1:当试图添加一个重复元素到hashset时,新元素并不会把旧元素替换掉,
而只是新元素不会添加到hashset(不会抛异常。)