Java集合类HashSet实现细节

来源:互联网 发布:淘宝修改销量代码 编辑:程序博客网 时间:2024/06/05 10:19

定义 

HashSet继承AbstractSet类,实现Set、Cloneable、Serializable接口。其中AbstractSet提供Set接口的骨干实现,从而最大限度地减少了实现此接口所需的工作。Set接口是一种不包括重复元素的Collection.它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null 元素。

对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。

  1. class Name  
  2. {  
  3.     private String first;   
  4.     private String last;   
  5.       
  6.     public Name(String first, String last)   
  7.     {   
  8.         this.first = first;   
  9.         this.last = last;   
  10.     }   
  11.   
  12.     public boolean equals(Object o)   
  13.     {   
  14.         if (this == o)   
  15.         {   
  16.             return true;   
  17.         }   
  18.           
  19.     if (o.getClass() == Name.class)   
  20.         {   
  21.             Name n = (Name)o;   
  22.             return n.first.equals(first)   
  23.                 && n.last.equals(last);   
  24.         }   
  25.         return false;   
  26.     }   
  27. }  
  28.   
  29. public class HashSetTest  
  30. {  
  31.     public static void main(String[] args)  
  32.     {   
  33.         Set<Name> s = new HashSet<Name>();  
  34.         s.add(new Name("abc""123"));  
  35.         System.out.println(  
  36.         s.contains(new Name("abc""123")));  
  37.     }  
  38. }   
上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。 

实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。 

细节剖析

1.为什么要重写equals方法和hashCode方法(技术实现原理):

程序向HashSet中添加一个对象时,先用hashCode方法计算出该对象的哈希码。

比较:

        (1),如果该对象哈希码与集合已存在对象的哈希码不一致,则该对象没有与其他对象重复,添加到集合中!

        (2),如果存在于该对象相同的哈希码,那么通过equals方法判断两个哈希码相同的对象是否为同一对象(判断的标准是:属性是否相同)

                1>,相同对象,不添加。

                2>,不同对象,添加!


此时会产生如下疑问:

1,为什么哈希码相同了还有可能是不同对象?

2,为什么经过比较哈希码还需要借助equals方法判断?

首先:

按照Object类的hashCode方法,是不可能返回两个相同的哈希码的。(哈希码唯一标志了对象

 然后:

Object类的hashCode方法返回的哈希码具有唯一性(地址唯一性),但是这样不能让程序的运行逻辑符合现实生活。(这个逻辑就是:属性相同的对象被看作同一个对象。)为了让程序的运行逻辑符合现实生活,Object的子类重写了hashCode的方法(基本数据类型的实现类都已经重写了两个方法,自定义的类要软件工程师自己重写)

重写的宗旨是什么?

重写就是为了实现这样的目的:属性相同的不同对象在调用其hashCode方法后,返回的是同样的哈希码。

但是

我们在重写的时候,发现几乎所有的写法都无法避免一个bug:有一些属性不同的对象(当然是不同的对象),会返回相同的哈希码。(即 重码)

最后:

为了解决这个问题:在哈希码相同的时候,再用equals方法比较两个对象的对应属性是否相同,这样,确保了万无一失。

这样:上面两个问题得到解决。


重写hashcode()和equals()方法后的代码

  1. class Name   
  2. {   
  3.     private String first;  
  4.     private String last;  
  5.     public Name(String first, String last)  
  6.     {   
  7.         this.first = first;   
  8.         this.last = last;   
  9.     }   
  10.     // 根据 first 判断两个 Name 是否相等  
  11.     public boolean equals(Object o)   
  12.     {   
  13.         if (this == o)   
  14.         {   
  15.             return true;   
  16.         }   
  17.         if (o.getClass() == Name.class)   
  18.         {   
  19.             Name n = (Name)o;  
  20.             // 使用String类中已经重写的equals()方法 
  21.             return n.first.equals(first);   
  22.         }   
  23.         return false;   
  24.     }   
  25.        
  26.     // 根据 first 计算 Name 对象的 hashCode() 返回值  
  27.     public int hashCode()   
  28.     {   
  29.         // 使用String类中已经重写的hashcode()方法
  30.         return first.hashCode();   
  31.     }  
  32.   
  33.     public String toString()   
  34.     {   
  35.         return "Name[first=" + first + ", last=" + last + "]";   
  36.     }   
  37.  }   
  38.    
  39.  public class HashSetTest2   
  40.  {   
  41.     public static void main(String[] args)   
  42.     {   
  43.         HashSet<Name> set = new HashSet<Name>();   
  44.         set.add(new Name("abc" , "123"));   
  45.         set.add(new Name("abc" , "456"));   
  46.         System.out.println(set);   
  47.     }   
  48. }  



0 0