首页 > 编程笔记

Java Set接口用法详解

Set 是 Collection 的子接口,Set 接口以散列的形式存储数据,所以元素没有顺序,它可以存储一组无序且唯一的对象。

Set 接口的定义为:
public interface Set<E> extends Collection<E>
实际开发中也不能直接实例化 Set,需要对其实现类进行实例化再完成业务操作。Set 的常用实现类主要有 HashSet、LinkedHashSet、TreeSet。

Set接口的实现类

HashSet 是开发中经常使用到的实现类,存储一组无序且唯一的对象,这里的无序是指元素的存储顺序和遍历顺序不一致。

HashSet 的定义为:
public class HashSet<E> extends AbstreactSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
下列代码演示了 HashSet 的使用。
public class Test {
   public static void main(String[] args) {
      HashSet hashSet = new HashSet();
      hashSet.add("Hello");
      hashSet.add("World");
      hashSet.add("Java");
      hashSet.add("Hello");
      System.out.println("hashSet的长度:"+hashSet.size());
      System.out.println("遍历hashSet");
      Iterator iterator = hashSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
      hashSet.remove("World");
      System.out.println("删除之后遍历hashSet");
      iterator = hashSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
   }
}
运行结果为:

hashSet的长度:3 
遍历hashSet 
Java,Hello,World, 
删除之后遍历hashSet 
Java,Hello,


LinkedHashSet 是 Set 的另外一个子接口,可以存储一组有序且唯一的元素。这里的有序是指元素的存储顺序和遍历顺序是一致的。

LinkedHashSet 的定义为:
public class LinkedHashSet<E> extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
下列代码演示了 LinkedHashSet 的使用:
public class Test {
   public static void main(String[] args) {
      LinkedHashSet linkedHashSet = new LinkedHashSet();
      linkedHashSet.add("Hello");
      linkedHashSet.add("World");
      linkedHashSet.add("Java");
      linkedHashSet.add("Hello");
      System.out.println("linkedHashSet的长度:"+linkedHashSet.size());
      System.out.println("遍历linkedHashSet");
      Iterator iterator = linkedHashSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
      linkedHashSet.remove("World");
      System.out.println("删除之后遍历linkedHashSet");
      iterator = linkedHashSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
   }
}
运行结果为:

linkedHashSet的长度:3 
遍历linkedHashSet 
Hello,World,Java, 
删除之后遍历linkedHashSet 
Hello,Java,


我们对集合执行了添加两个“Hello”的操作,但是只保存了一个。这是因为 LinkedHashSet 集合的元素是唯一的,即不能出现两个相等的元素。字符串如此,其他对象也是一样的。我们定义一个 A 类,将类的实例化对象存入集合,代码为:
public class Test {
   public static void main(String[] args) {
      LinkedHashSet set = new LinkedHashSet();
      set.add(new A(1));
      set.add(new A(1));
      Iterator iterator = set.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
   }
}

class A{
   private int num;
   public A(int num) {
      this.num = num;
   }
   @Override
   public String toString() {
      return "A [num=" + num + "]";
   }
}
运行结果为:

A [num=1],A [num=1],

你可以看到两个 A 对象都保存到了集合中,也就是说当前集合不认为这两个对象相等。那么程序是如何来鉴别两个对象是否相等的呢?通过继承自 Object 类的 equals() 方法来判断,Object 类的 equals() 方法定义如下:
public boolean equals(Object obj){
    return (this == obj);
}
“==”表示比较两个对象的内存地址,所以虽然两个 A 对象的 num 值相等,也就是从内容的角度来看是相等的,但是内存地址不同,所以程序会认为是不相等的两个对象。

现在有一个需求,只要两个对象的 num 值相等,就认为是同一个对象,那如何修改代码呢?说明一下用 LinkedHashSet 判断两个对象是否相等的原理,首先会判断两个对象的 hashCode 是否相等,什么是hashCode?简单来说就是将对象的内部信息(如内存地址、属性值等),通过某种特定规则转换成一个散列值,也就是该对象的 hashCode。两个不同对象的 hashCode 可能相等,但是 hashCode 不相等的两个对象一定不是同一个对象。

所以集合在判断两个对象是否相等时,会先比较它们的 hashCode,如果不相等,则认为不是同一个对象,可以添加。如果 hashCode 相等,还不能认为两个对象就是相等的,需要通过 equals() 方法进一步判断。如果 equals() 方法为 true,则不会重复添加;如果 equals() 方法为 false,则正常添加。先判断 hashCode 是否相等可以减少 equals() 方法的调用,提高效率。所以两个 A 相等的前提是 hashCode 相等,且 equals() 方法返回 true,修改后的程序如下:
class A{
   ……
   @Override
   public boolean equals(Object obj) {
      // TODO Auto-generated method stub
      return true;
   }
   @Override
   public int hashCode() {
      // TODO Auto-generated method stub
      return 1;
   }
}
再次运行程序,结果为:

A [num=1],

程序显示只存储了一个 A 对象,在 Set 的子接口中,除了 LinkedHashSet 可以存放有序元素之外,TreeSet 中保存的元素也是有序的,并且 TreeSet 的有序和 LinkedHashSet 的有序有所不同。LinkedHashSet 的有序是指元素的存储顺序和遍历顺序是一致的,即元素按什么顺序存进去,遍历时就按什么顺序输出。TreeSet 的有序是指集合内部会自动给所有的元素按照升序进行排列,即无论存入元素的顺序是什么,遍历时会按照升序进行输出。

TreeSet 中的元素也是唯一的,定义如下:
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
下面的程序演示了 TreeSet 的使用:
public class Test {
   public static void main(String[] args) {
      TreeSet treeSet = new TreeSet();
      treeSet.add(1);
      treeSet.add(3);
      treeSet.add(6);
      treeSet.add(2);
      treeSet.add(5);
      treeSet.add(4);
      treeSet.add(1);
      System.out.println("treeSet的长度:"+treeSet.size());
      System.out.println("遍历treeSet");
      Iterator iterator = treeSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
      treeSet.remove(5);
      System.out.println("删除之后遍历treeSet");
      iterator = treeSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
   }
}
运行结果为:

treeSet的长度:6 
遍历treeSet 
1,2,3,4,5,6, 
删除之后遍历treeSet 
1,2,3,4,6,


因为 TreeSet 内部会自动按照升序对元素进行排列,所以添加到 TreeSet 集合中的元素必须具备排序的功能,现在我们创建一个类 A,同时将 A 的实例化对象保存到 TreeSet 中,代码如下:
public class Test {
   public static void main(String[] args) {
      TreeSet treeSet = new TreeSet();
      treeSet.add(new A(1));
      treeSet.add(new A(3));
      treeSet.add(new A(6));
      treeSet.add(new A(2));
      treeSet.add(new A(5));
      treeSet.add(new A(4));
      treeSet.add(new A(1));
      System.out.println("treeSet的长度:"+treeSet.size());
      System.out.println("遍历treeSet");
      Iterator iterator = treeSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
      treeSet.remove(new A(5));
      System.out.println("删除之后遍历treeSet");
      iterator = treeSet.iterator();
      while(iterator.hasNext()) {
         System.out.print(iterator.next()+",");
      }
   }
}

class A{
   private int num;
   public A(int num) {
      this.num = num;
   }
}
运行结果如下图所示:


报错原因是 A 不具备排序的功能,如何解决呢?让 A 实现 Comparable 接口即可,代码如下所示:
class A implements Comparable{
   ……
   @Override
   public int compareTo(Object o) {
      // TODO Auto-generated method stub
      /**
       * A.compareTo(B)
       * 返回值:
       * 1表示A大于B
       * 0表示A等于B
       * -1表示A小于B
       */
      A a = (A) o;
      if(this.num > a.num) {
         return 1;
      }else if(this.num == a.num) {
         return 0;
      }else {
         return -1;
      }
   }
   @Override
   public String toString() {
      return "A [num=" + num + "]";
   }
}
再次运行,结果为:

treeSet的长度:6 
遍历treeSet 
A [num=1],A [num=2],A [num=3],A [num=4],A [num=5],A [num=6],
删除之后遍历treeSet 
A [num=1],A [num=2],A [num=3],A [num=4],A [num=6],

推荐阅读