集合接口
原文: https://docs.oracle.com/javase/tutorial/collections/interfaces/set.html
Set 是 Collection ,不能包含重复元素。它模拟了数学集抽象。 Set接口仅包含从Collection继承的方法,并添加了禁止重复元素的限制。 Set还为equals和hashCode操作的行为增加了一个更强的契约,允许Set实例有意义地进行比较,即使它们的实现类型不同。如果两个Set实例包含相同的元素,则它们是相等的。
Java 平台包含三个通用Set实现:HashSet,TreeSet和LinkedHashSet。 HashSet 将其元素存储在哈希表中,是性能最佳的实现;但它不能保证迭代的顺序。 TreeSet 将其元素存储在红黑树中,根据其值对其元素进行排序;它比HashSet慢得多。 LinkedHashSet ,作为哈希表实现,其中链接列表贯穿其中,根据它们插入集合(插入顺序)的顺序对其元素进行排序。 LinkedHashSet使客户免于HashSet提供的未指定的,通常混乱的订单,成本仅略高。
这是一个简单但有用的Set成语。假设您有Collection,c,并且您想要创建另一个包含相同元素的Collection,但会删除所有重复项。下面的单线程就可以了。
Collection<Type> noDups = new HashSet<Type>(c);
它的工作原理是创建一个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);}
设置接口基本操作
size操作返回Set(其基数)中的元素数。 isEmpty方法完全按照您的想法进行。 add方法将指定的元素添加到Set(如果它尚不存在)并返回一个布尔值,指示是否添加了元素。类似地,remove方法从Set中删除指定的元素(如果存在)并返回指示元素是否存在的布尔值。 iterator方法在Set上返回Iterator。
以下 program 打印出其参数列表中的所有不同单词。提供了该程序的两个版本。第一个使用 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]
请注意,代码始终通过其接口类型(Set)而不是其实现类型引用Collection。这是强烈推荐的编程实践,因为它使您可以灵活地仅通过更改构造器来更改实现。如果用于存储集合的变量或用于传递它的参数被声明为Collection的实现类型而不是其接口类型,所有这样的变量和参数必须是已更改以更改其实现类型。
此外,无法保证生成的程序能够正常运行。如果程序使用原始实现类型中存在但未在新实现类型中存在的任何非标准操作,则程序将失败。仅通过其接口引用集合可防止您使用任何非标准操作。
前面例子中Set的实现类型是HashSet,它不能保证Set中元素的顺序。如果您希望程序按字母顺序打印单词列表,只需将Set的实现类型从HashSet更改为TreeSet。进行这个简单的单行更改会导致前一个示例中的命令行生成以下输出。
java FindDups i came i saw i left4 distinct words: [came, i, left, saw]
设置接口批量操作
批量操作特别适合Set;应用时,它们执行标准的集合代数运算。假设s1和s2是集合。以下是批量操作的作用:
s1.containsAll(s2)- 如果s2是s1的子集,则返回true。 (s2是s1的子集,如果设置s1包含s2中的所有元素。)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);
前面的习语中的结果Set的实现类型是HashSet,正如已经提到的,它是 Java 平台中最好的全面Set实现。但是,任何通用Set实现都可以替代。
让我们重新审视FindDups程序。假设您想知道参数列表中的哪些单词只出现一次,哪些出现多次,但您不希望重复打印任何重复项。这种效果可以通过生成两个集合来实现 - 一个集合包含参数列表中的每个单词,另一个集合仅包含重复项目。仅出现一次的单词是这两组的集合差异,我们知道如何计算。这是 the resulting program 的样子。
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-differenceuniques.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);
设置接口阵列操作
对于Set,数组操作不会对其他任何Collection做任何特殊操作。这些操作在收集接口部分中描述。
