Set是一个不能包含重复元素的Collection,它模拟了数学集抽象,Set
接口仅包含从Collection
继承的方法,并添加禁止重复元素的限制,Set
还为equals
和hashCode
操做的行为添加了一个更强的契约,容许Set
实例有意义地进行比较,即便它们的实现类型不一样,若是两个Set
实例包含相同的元素,则它们是相等的。html
Java平台包含三个通用的Set实现:HashSet
、TreeSet
和LinkedHashSet
。将其元素存储在哈希表中的HashSet是性能最佳的实现,但它不能保证迭代的顺序。TreeSet将其元素存储在红黑树中,根据元素的值对其元素进行排序,它比HashSet
慢得多。LinkedHashSet实现为哈希表,其中有一个链表,根据它们插入集合的顺序(插入顺序)对其元素进行排序,LinkedHashSet
让它的客户端避免了HashSet
提供的未指定的、一般混乱的排序,但代价只稍微高一点。java
这是一个简单但有用的Set
语法,假设你有一个Collection
,c
,而且你想要建立另外一个包含相同元素的Collection
,但会删除全部重复项,下面的一行代码就能够解决这个问题。git
Collection<Type> noDups = new HashSet<Type>(c);
它的工做原理是建立一个Set
(根据定义,它不能包含重复项),初始化包含c
中的全部元素,它使用Collection接口部分中描述的标准转换构造函数。github
或者,若是使用JDK 8或更高版本,你能够使用聚合操做轻松收集到Set
:编程
c.stream() .collect(Collectors.toSet()); // no duplicates
这是一个稍长的示例,它将名称Collection
累积到TreeSet
中:segmentfault
Set<String> set = people.stream() .map(Person::getName) .collect(Collectors.toCollection(TreeSet::new));
如下是第一个语法的次要变体,它在删除重复元素时保留了原始集合的顺序:api
Collection<Type> noDups = new LinkedHashSet<Type>(c);
如下是封装前面的语法的泛型方法,返回与传递的相同的泛型类型的Set
。数组
public static <E> Set<E> removeDups(Collection<E> c) { return new LinkedHashSet<E>(c); }
size
操做返回Set
中的元素数(其基数),isEmpty
方法彻底符合你的想法,add
方法将指定的元素添加到Set
(若是它尚不存在)并返回一个布尔值,指示是否添加了元素。相似地,remove
方法从Set
中删除指定的元素(若是存在)并返回一个布尔值,指示元素是否存在,iterator
方法在Set
上返回Iterator
。oracle
如下程序打印出其参数列表中的全部不一样单词,提供了该程序的两个版本,第一个使用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
,它不保证Set
中元素的顺序,若是你但愿程序按字母顺序打印单词列表,只需将Set
的实现类型从HashSet
更改成TreeSet
,进行这个简单的单行更改会致使前一个示例中的命令行生成如下输出。
java FindDups i came i saw i left 4 distinct words: [came, i, left, saw]
批量操做特别适合于Set
,应用时,它们执行标准的集代数运算,假设s1
和s2
是Set
,批量操做是这样作的:
s1.containsAll(s2
) — 若是s2
是s1
的子集,则返回true
(若是set
s1
包含s2
中的全部元素,则s2
是s1
的子集)。s1.addAll(s2)
— 将s1
转换为s1
和s2
的并集(两个集合的并集是包含任一集合中包含的全部元素的集合)。s1.retainAll(s2)
— 将s1
转换为s1
和s2
的交集(两个集合的交集是仅包含两个集合共有的元素的集合)。s1.removeAll(s2)
— 将s1
转换为s1
和s2
的(非对称)差集(例如,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);
前面的语法中的结果集的实现类型是HashSet
,如前所述,它是Java平台中最好的全能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]
不太常见的集代数运算是对称差集 — 包含在两个指定集合中但不一样时包含在两个集合中的元素的集合,如下代码非破坏性地计算两个集合的对称差集。
Set<Type> symmetricDiff = new HashSet<Type>(s1); symmetricDiff.addAll(s2); Set<Type> tmp = new HashSet<Type>(s1); tmp.retainAll(s2); symmetricDiff.removeAll(tmp);
除了对其余任何Collection
执行的操做以外,数组操做不会对Set
执行任何特殊操做,Collection接口部分介绍了这些操做。