`
d02540315
  • 浏览: 32278 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

Set是如何实现"没有重复元素" - hashCode compareTo equals

    博客分类:
  • JDK
阅读更多
Set和数学中的集合是同一个概念,就是没有重复元素的集合。

这篇文章主要论述了Set是如何实现"没有重复元素"(no duplicate elements)的,以及阐述了什么是“重复”(duplicate),是相同的地址空间?是equals的返回值为true?是compareTo的返回值为0 ?还是有相同的hashCode?本文还给出了在什么情况下使用什么样的Set的建议。

注:本文不涉及范型。

  • 1、树形结构:

public interface Set<E> extends Collection<E>{}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>{}
public class CopyOnWriteArraySet<E>extends AbstractSet<E>implements Serializable{}
public abstract class EnumSet<E extends Enum<E>>extends AbstractSet<E>implements Cloneable, Serializable{}
public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, Serializable{}
public final class JobStateReasonsextends HashSet<JobStateReason>implements PrintJobAttribute{}
public class LinkedHashSet<E>extends HashSet<E>implements Set<E>, Cloneable, Serializable{}
public class TreeSet<E>extends AbstractSet<E>implements SortedSet<E>, Cloneable, Serializable{}

   可以看出,可以实例化的类为:CopyOnWriteArraySet,HashSet,LinkedHashSet,TreeSet。

  • 2、Set是如何实现元素唯一性的

   javadoc中对Set的描述第一段如下:“A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1
   and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.”
   这段话是对是错,请看下面分析。
   要进行下面的论述,我们先了解一下Map。Map中的元素是“键-值”对,其中“键”必须是唯一的。TreeSet和HashSet就是利用这个特性实现“no duplicate    elements”。它把set中的元素作为Map中的“键”,从而保持元素的唯一性。这些键在Map中又是如何区分的呢?不同的Map有不同的做法,而且区别很大。
   下面我们分别就TreeSet、HashSet和CopyOnWriteArraySet进行论述:

  • 2.1、TreeSet部分:

   以下以TreeSet为例进行分析。
   请看TreeSet的部分实体:
public class TreeSet<E> extends AbstractSet<E>
      implements SortedSet<E>, Cloneable, java.io.Serializable
{
  // The backing Map
      private transient SortedMap<E,Object> m;
      // The keySet view of the backing Map
      private transient Set<E> keySet;
      // Dummy value to associate with an Object in the backing Map
      //这是每个键所指的对像
      private static final Object PRESENT = new Object();
      //constructor
      private TreeSet(SortedMap<E,Object> m) {
          this.m = m;
           keySet = m.keySet();
      }
      public TreeSet() {
   this(new TreeMap<E,Object>());
      }
      //以下省略..........
}

    可以看到TreeSet使用了SortedMap作为其Map保存“键-值”对,而这个SortedMap的真正实体是TreeMap。
  
    请看示例程序1:
import java.util.*;
public class SetTest1 {
  public static void main(String[] args){
   Set set = new TreeSet();
   set.add(new SetElement1("aa"));
   set.add(new SetElement1("bb"));
  }
  static class SetElement1{
   String s;
   public SetElement1(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement1)obj).s);
   }
  }
}

    该程序能够正常编译,但是运行时会抛出异常java.lang.ClassCastException。为什么?
  
    请看示例程序2:
import java.util.*;
public class SetTest2 {
  public static void main(String[] args){
   Set set = new TreeSet();
   set.add(new SetElement2("aa"));
   set.add(new SetElement2("aa"));
   set.add(new SetElement2("bb"));
   System.out.println(set);
  }
  static class SetElement2 implements Comparable{
   String s;
   public SetElement2(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public int compareTo(Object o){
    return s.compareTo(((SetElement2)o).s);
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement2)obj).s);
   }
  }
}

   运行结果:
   [aa, bb]
   这正是我们所期望的结果。那“示例程序1”和“示例程序2”有什么区别?
   是因为SetElement2实现了Comparable接口,而SetElement1没有。SetElement2实现Comparable接口有什么用呢?因为在TreeSet的add方法中需要比较两个    元素的“值”。请看TreeMap中的compare方法:
   private int compare(K k1, K k2) {
        return (comparator==null ? ((Comparable</*-*/K>)k1).compareTo(k2)
                                 : comparator.compare((K)k1, (K)k2));
   }

   可见这个方法先把要比较的元素down cast成Comparable类型。这里就可以解释“示例程序1”中为什么会抛出异常java.lang.ClassCastException,因SetElement1没有实现Comparable接口,当然就不能down cast成Comparable。可见,要用TreeSet来做为你的Set,那么Set中所装的元素都必须实现了Comparable接口。
   说到这里,你是不是想到了TreeSet中是采用Comparable接口中的compareTo方法来判断元素是否相同(duplicate),而不是采用其他类似equals之类的东东来判断。
 
   请看示例程序3:
    import java.util.Set;
import java.util.*;
public class SetTest3 {
  public static void main(String[] args){
   Set set = new HashSet();
   set.add(new SetElement3("aa"));
   set.add(new SetElement3("aa"));
   set.add(new SetElement3("bb"));
   System.out.println(set);
  }
  static class SetElement3 implements Comparable{
   String s;
   public SetElement3(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public int compareTo(Object o){
    //return s.compareTo(((SetElement3)o).s);
    return -1;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement3)obj).s);
   }
  }
}

   运行结果:
   [bb, aa, aa]
   看到没有,有两个“aa”!!这是因为compareTo返回值始终是"-1",也就是说“把任何元素都看成不同”。
 
   综上所述,你是否对javadoc中对Set功能的描述有了怀疑?!

  • 2.2、HashSet部分:

   以下以HashSet为例进行分析。
   从Hashset类的主体部分:
public class HashSet<E> extends AbstractSet<E>
     implements Set<E>, Cloneable, java.io.Serializable
{
  static final long serialVersionUID = -5024744406713321676L;
  private transient HashMap<E,Object> map;
  // Dummy value to associate with an Object in the backing Map
  //这是每个键所指的对像
  private static final Object PRESENT = new Object();


     public HashSet() {
   map = new HashMap<E,Object>();
      }
     public boolean add(E o) {
   return map.put(o, PRESENT)==null;
      }
    //以下省略..........
    }

        public HashSet() {

  map = new HashMap<E,Object>();
   
}

   可以看到HashSet使用了HashMap作为其Map保存“键-值”对。
 
   请看示例程序4:
import java.util.*;

public class SetTest4 {
public static void main(String[] args){
  Set set = new HashSet();
  set.add(new SetElement4("aa"));
  set.add(new SetElement4("aa"));
  set.add(new SetElement4("bb"));
  System.out.println(set);
}
static class SetElement4{
  String s;
  public SetElement4(String s){
   this.s =  s;
  }
  public String toString(){
   return s;
  }
  public boolean equals(Object obj) {
   return s.equals(((SetElement4)obj).s);
  }
}
}


   运行结果:
   [bb, aa, aa]
   没有“示例程序1”中的java.lang.ClassCastException,但是运行结果似乎不对,因为有两个“aa”。
 
   请看示例程序5:
import java.util.*;
public class SetTest5 {
  public static void main(String[] args){
   Set set = new HashSet();
   set.add(new SetElement5("aa"));
   set.add(new SetElement5("aa"));
   set.add(new SetElement5("bb"));
   System.out.println(set);
  }
  static class SetElement5{
   String s;
   public SetElement5(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement5)obj).s);
   }
   public int hashCode() {
    //return super.hashCode();
    return s.hashCode();
   }
  }
}

    运行结果:
    [bb, aa]
    这就对了。“示例程序4”和“示例程序5”有什么区别?是SetElement5重写了hashCode方法。
  
    可见HashSet中是采用了比较元素hashCode的方法来判断元素是否相同(duplicate),而不是采用其他类似equals之类的东东来判断。
  
    说了这么多,那java类库中到底有没有根据equals来判断元素是否相同(duplicate)的Set呢?请看下文。

  • 2.3、CopyOnWriteArraySet部分:

   类CopyOnWriteArraySet是java.util.concurrent包中的一个类,所以它是线程安全的。
   CopyOnWriteArraySet是使用CopyOnWriteArrayList作为其盛放元素的容器。当往CopyOnWriteArrayList添加新元素,它都要遍历整个List,并且用equals来    比较两个元素是否相同。

   请看示例程序6:
import java.util.*;
import java.util.concurrent.*;
public class SetTest6 {
  public static void main(String[] args){
   Set set = new CopyOnWriteArraySet();
   set.add(new SetElement6("aa"));
   set.add(new SetElement6("aa"));
   set.add(new SetElement6("bb"));
   System.out.println(set);
  }
  static class SetElement6{
   String s;
   public SetElement6(String s){
    this.s =  s;
   }
   public String toString(){
    return s;
   }
   public boolean equals(Object obj) {
    return s.equals(((SetElement6)obj).s);
   }
  }
}

   运行结果:
   [aa, bb]
   好了,一切搞定!!

  • 3、总结:

   Javadoc中的一些描述可能是不准确的,大家要当心了!
 
   Set中实现元素互异的各种方法差异很大,大致可以分为三种:使用equals,使用hashCode,使用compareTo。但是我还没有发现采用“判断地址空间是否相同”来判断元素是否相同的类,当然我们可以用现有的三种方法来实现“判断地址空间是否相同”。
 
   综上所述,我们可以总结出使用Set的三种不同的情形:(以下假设元素类为Element)
   A、如果想使用Element的equals方法来判断元素是否相同,那么可以使用CopyOnWriteArraySet来构造类的实体。
   B、如果Element实现了Comparable接口,而且想使用compareTo方法来判断元素是否相同,那么可以使用TreeSet来构造类的实体。
   C、如果想使用判断hashCode是否相同的方法来判断元素是否相同,那么可以使用HashSet来构造类的实体
分享到:
评论

相关推荐

    对Java中Set的深入研究.pdf

    Java中的Set接口是基于集合概念实现的,它不包含重复元素。Set接口继承自Collection接口,并提供了多种实现类,如HashSet、LinkedHashSet、TreeSet和CopyOnWriteArraySet等。这些实现类各自有不同的特性和使用场景。...

    java中set接口使用方法详解

    如果两个对象的`hashCode()`相等并且`equals()`返回true,那么它们被视为同一个对象,HashSet将不会存储重复的元素。在上面的代码示例中,`Student`类重写了`hashCode()`和`equals()`方法,确保了相同id的学生被视为...

    TreeSet判断重复元素解析及代码示例

    当向`TreeSet`添加元素时,如果两个元素的`compareTo()`返回值为0,那么`TreeSet`会认为这两个元素是相等的,因此不会添加重复元素。 在给定的代码示例中,创建了一个自定义类`Combine`,实现了`Comparable...

    set集合的基本特点,set集合底层去重原理,集合怎么进行排序

    Set集合在Java编程语言中是一种基础且重要的数据结构,它主要特点是存储不重复的元素,且没有特定的插入顺序。接下来我们将深入探讨Set集合的基本特点、底层去重原理以及如何进行排序。 首先,让我们理解Set集合的...

    Java 实例 - 集合比较源代码-详细教程.zip

    - `Set`: 不允许重复元素的集合,没有特定的顺序。 - `Queue`: 代表一种先进先出(FIFO)的数据结构。 2. **具体实现类** - `ArrayList` 和 `LinkedList`: 分别基于动态数组和双向链表实现的`List`接口,它们在...

    day03_List、Set 每日作业卷答案1

    3. **重复元素**:List接口允许集合中存在重复的元素,判断重复是通过元素的`equals()`方法来完成的。如果两个元素的`equals()`返回`true`,则认为它们是相同的元素。 在处理List时,有一些特定的方法用于操作元素...

    超详细的Java面试题总结(三)之Java集合篇常见问题.docx

    HashSet通过比较元素的hashcode和equals()方法来检测重复项。HashMap则是一个存储键值对的结构,不保证顺序,允许键或值为null。 HashMap和ConcurrentHashMap的主要差异在于线程安全性和设计。ConcurrentHashMap...

    Java软件开发实战 Java基础与案例开发详解 11-3 Set接口实现类 共19页.pdf

    在Java集合框架中,`Set`接口是`Collection`接口的子接口,它不允许包含重复元素。本文将详细介绍`Set`接口及其三种主要实现类:`HashSet`、`LinkedHashSet`和`TreeSet`。 ### 11.3.1 实现类HashSet `HashSet`类是...

    JAVA集合类[参考].pdf

    在描述中提到了`Set`集合,它是不允许有重复元素的,并且在某些实现中可以提供特定的排序。 `Set`接口有两个常见的实现类:`HashSet`和`TreeSet`。`HashSet`基于散列(哈希)表,通过`hashCode()`方法快速定位元素...

    java中List对象列表实现去重或取出及排序的方法

    HashSet是一个不允许重复元素的集合,添加元素时,会自动忽略重复元素。 ```java List&lt;Student&gt; list = new ArrayList(); // 添加元素到list Set&lt;Student&gt; set = new HashSet(list); list.clear(); list.addAll(set...

    Java集合容器面试题(2022最新版)-重点.docx

    - 如果`List`中的元素实现了`Comparable`接口,则自动使用该接口的`compareTo()`方法排序。 - 如果需要自定义排序规则,可以提供`Comparator`对象。 以上是对给定文件中Java集合容器面试题知识点的详细解释,希望能...

    新建文本文档复习.txt

    - **HashSet 特点**:`HashSet` 是 `Set` 接口的一个实现,不允许包含重复元素;它不保证集合中元素的顺序;并且它是非同步的。 - **实现去重逻辑**:在代码中,为了确保 `HashSet` 中存储的对象是唯一的,需要对 `...

    day03_List、Set 每日作业卷1

    如果两个元素的哈希值相同并且equals()返回true,那么HashSet会认为这两个元素是相同的,不会再次添加。 【数据结构元素存取特点】 1. **数组**:存取速度快,但大小固定,插入和删除元素效率低。 2. **链表**:...

    HashSet,TreeSet和LinkedHashSet的区别1

    在Java编程语言中,集合框架是处理数据的重要工具,而Set接口是其中的一个关键部分,它不允许重复元素。本文主要探讨了三种基于Set接口的实现类:HashSet、LinkedHashSet和TreeSet,它们各自有不同的特性和使用场景...

    集合部分总结

    - **HashSet**:使用哈希表实现,通过`hashCode()`和`equals()`方法来判断元素的相等性。 - **TreeSet**:实现了`SortedSet`接口,能够根据元素的自然顺序或者自定义比较器进行排序,内部使用红黑树结构实现。 ##...

    【List、Set、数据结构、Collections】.pdf

    如果需要在HashSet中存储不可变对象,则需要确保对象的相等性判断依据(equals方法)和哈希码(hashCode方法)的正确实现。 可变参数的格式是在方法声明中使用省略号“...”来表示方法可以接受任意数量的参数,这些...

    Java习题六.docx

    在添加元素时,需要重写 hashCode() 和 equals() 方法来比较元素。 2. 选择合适的 Map 集合保存 5 位学员的学号和姓名,然后按学号的自然顺序的倒序将这些键值对打印出来。 知识点:TreeMap 是一种有序的 Map 集合...

    JAVA基础-集合类

    - **Set**:不允许重复元素,不保证特定的顺序。 - **HashSet**:使用哈希表实现,提供快速的添加和查找操作。 - **TreeSet**:使用红黑树实现,可以保持元素的自然排序或自定义排序。 - **LinkedHashSet**:...

    java 中HashMap、HashSet、TreeMap、TreeSet判断元素相同的几种方法比较

    因此,HashSet判断元素是否重复的方式与HashMap类似:首先计算元素的哈希值,然后通过equals()方法检查是否存在相同的元素。 **TreeMap** TreeMap是一个有序的键值对存储结构,它根据键的自然顺序或者自定义比较器...

Global site tag (gtag.js) - Google Analytics