Set

Set接口

Set是不能包含重复元素的集合。它是对数学的集合进行的抽象建模。Set接口只包含了从Collection继承来的方法,没有其它额外方法, 然后增加了不允许重复元素这一限制。Set还对equalshashCode方法行为进行了重写, 这样即使两个set的实现类型不一样,他们还是能够相互比较的。如果两个set包含的元素一样,它们就是相等的。

Java平台有三个对Set的基本实现类:HashSet, TreeSet, 和LinkedHashSetHashSet是用哈希表存储元素的,它的实现方式在性能上是最优的,但是不能保证迭代出来的元素顺序是固定的。TreeSet是用红黑树(什么是红黑树?:))来存储元素的,根据元素的值来排序的,性能仅次于HashSetLinkedHashSet是用哈希表以及关联到上面的链表实现的,根据元素的插入顺序来排序的(插入顺序)。LinkedHashSet去除了HashSet的排序不确定性这个缺陷,为之付出的是性能降低了。

下面是一些简单但是非常有用的Set的惯用方式。假定你有一个Collection c,你想要另外一个Collection和这个一样,只是把重复的元素去掉,下面的一条语句就能满足你的心愿:

Collection<Type> noDups = new HashSet<Type>(c);

它是通过创建一个Set来实现的 (Set就是不能包含重复元素),初始化的时候是给了c的所有元素。使用的是标准的构造方法转换, 更多细节可以看这里

如果你现在使用的是JDK 8或者更高版本,你可以用聚合操作轻松的收集到一个Set中去:

c.stream().collect(Collectors.toSet()); // no duplicates

这里有一个更长点的例子,它会把一个Collection里面的名字放到一个TreeSet中去:

Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));

下面对第一种方法做了小小的改动,在去掉重复元素的同时,还能保证原来的顺序不变。

Collection<Type> noDups = new LinkedHashSet<Type>(c);

接下来是泛型的实现方式,会返回一个和传进来类型一致的Set:

public static <E> Set<E> removeDups(Collection<E> c) {
    return new LinkedHashSet<E>(c);
}

Set接口的基本操作

size返回当前Set的元素个数(基数)。isEmpty顾名思义,正如你想的那样是判断空的。add方法是把一个指定的元素加到Set中去(如果Set中不存在这个元素)并且返回一个布尔类型说明元素有没有添加成功。同样的,remove方法是把指定的元素从Set中移去(如果存在的话)并且返回一个希尔类型说明这个元素是不是存在。iterator方法返回Set的迭代。

下面的程序会输出运行参数列表里的单词(去重复),有两个版本的程序。第一个用JDK 8的聚合函数,第二个用的是for-each循环。

使用JDK 8的聚合操作:

import java.util.*;
import java.util.stream.*;

public class FindDups {
    public static void main(String[] args) {
        Set<String> distinctWords = Arrays.asList(args).stream()
        .collect(Collectors.toSet()); 
        System.out.println(distinctWords.size()+ 
                           " distinct words: " + 
                           distinctWords);
    }
}

使用for-each循环

import java.util.*;

public class FindDups {
    public static void main(String[] args) {
        Set<String> s = new HashSet<String>();
        for (String a : args)
               s.add(a);
               System.out.println(s.size() + " distinct words: " + s);
    }
}

现在用下面的命令把每个版本都执行一下:

java FindDups i came i saw i left

执行结果如下:

4 distinct words: [left, came, saw, i]

请注意,在代码里我们一直用的是Collection的接口类型,也就是Set,而不是引用具体的实现类。 强烈建议养成这样的编程习惯,因为对于这样的代码,如果要改变其它实现类,只要轻松的改变一下调用构造方法的地方就可以了。
相反,如果你在存储collection的变量地方或者传递的参数地方写的是具体的实现类,而不是接口, 那么我要改变成其它的实现类, 变量和传递参数都要修改。而且,你不能保证这样的代码在其它情况下也能工作,如果你的程序调用了Set非标准的方法,这些方法只在当前实现类里存在,而换成一个新的实现类,没有这些方法,程序就出错了。如果你在程序里只引用了接口,所有方法都是接口的标准方法,那么自然也不会调用到实现类的具体方法,程序更强大。

前面的例子中,我们用到的是HashSet,它不能保证里面的元素顺序是固定的,如果你想输出结果按字母顺序排序,只要简单的把HashSet换成TreeSet。只要稍微改到一行,就能得到如下结果:

java FindDups i came i saw i left

4 distinct words: [came, i, left, saw]

Set接口的批量操作

批量操作非常适用于集合与集合之间,可以用于执行标准的集合代数操作。假定s1s2是集合,下面是集合的批量操作。

  • s1.containsAll(s2)-如果s2s1的子集就返回真。(如果s1包含了s2的所有 元素,我们就把s2称作s1的子集)
  • s1.addAll(s2)-把s1转变成s1,s2的合集。(两个集合的合集包含每个集合的所有元素)
  • s1.retainAll(s2)-把s1转变成s1s2的交集。(两个集合的交集仅包含两个集合都有的元素)
  • s1.removeAll(s2)-把s1转变成s1s2的差集(asymmetric:非对称差集,集合的差集有对称和非对称两种,此操作是非对称,对称参看本文的末尾)。(例如两个集合的差集s1-s2包含所有在s1但不在s2的元素)

如果计算两个集合的合集、交集和差集,又不想改变每一个集合的原有结构和数据。那么在调用批量操作之前,你需要复制一个集合。看下面的使用技巧:

Set<Type> union = new HashSet<Type>(s1);
union.addAll(s2);

Set<Type> intersection = new HashSet<Type>(s1);
intersection.retainAll(s2);

Set<Type> difference = new HashSet<Type>(s1);
difference.removeAll(s2);

在上面的例子中,我们使用的Set的实现类是HashSet, 前面也提到过,它是Set所有实现类中性能最好的。当然了,其它的Set实现类都可以用。

让我们再来看一下FindDups这个程序。假如你想知道参数列表的单词哪些只出现过一次,哪些出现过多次,而且你也不想任何重复的单词多次打印出来。那么我们就可以用两个集合来达到这种效果。一个集合包含列表中的每一个单词,另一个集合只包含重复出现的单词。那么这两个集合的差集就是只出现一次的单词,差集我们已经知道如何计算,下面是程序示例:

import java.util.*;

public class FindDups2 {
    public static void main(String[] args) {
        Set<String> uniques = new HashSet<String>();
        Set<String> dups    = new HashSet<String>();

        for (String a : args)
            if (!uniques.add(a))
                dups.add(a);

        // Destructive set-difference
        uniques.removeAll(dups);

        System.out.println("Unique words:    " + uniques);
        System.out.println("Duplicate words: " + dups);
    }
}

用之前的参数(i came i saw i left)再来运行程序,就会得到下面的结果:

Unique words:    [left, saw, came]
Duplicate words: [i]

一个不太常用的集合代数操作就是对称差集(symmetric set difference ), 对称差集合包含两个集合中所有的元素并去除两个集合都含有的那些元素(并集减去交集)。下面的代码计算两个集合的对称差集,并且不破坏原来的集合结构和数据:

Set<Type> symmetricDiff = new HashSet<Type>(s1);
symmetricDiff.addAll(s2);
Set<Type> tmp = new HashSet<Type>(s1);
tmp.retainAll(s2);
symmetricDiff.removeAll(tmp);

Set接口的数组操作

对Set来说,数组操作没有做任何扩展,Collection有什么,它就有什么。有关描述,可以参看The Collection Interface

原文地址:
http://docs.oracle.com/javase/tutorial/collections/interfaces/set.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值