共计 3747 个字符,预计需要花费 10 分钟才能阅读完成。
Set 接口
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 中的所有元素,它使用 Collection 接口部分中描述的标准转换构造函数。
或者,如果使用 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(如果它尚不存在)并返回一个布尔值,指示是否添加了元素。类似地,remove 方法从 Set 中删除指定的元素(如果存在)并返回一个布尔值,指示元素是否存在,iterator 方法在 Set 上返回 Iterator。
以下程序打印出其参数列表中的所有不同单词,提供了该程序的两个版本,第一个使用 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 接口批量操作
批量操作特别适合于 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);
Set 接口数组操作
除了对其他任何 Collection 执行的操作之外,数组操作不会对 Set 执行任何特殊操作,Collection 接口部分介绍了这些操作。
上一篇:Collection 接口