import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> seen = new HashSet<>();
System.out.println(seen.add(7)); // true - 7 was new
System.out.println(seen.add(7)); // false - already there
System.out.println(seen);
}
}
这个返回值非常有用:if (!seen.add(x)) { /* x is a repeat */ } 让你在遍历的同时用一行代码检测出重复项。
给列表去重
因为集合会拒绝重复值,给一个集合类去重最快的方法就是把它整个倒进 Set 里。HashSet 的构造函数接受任何其他集合:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Ada", "Boris", "Ada", "Chen", "Boris"));
Set<String> unique = new HashSet<>(names);
System.out.println(unique); // each name once
System.out.println(unique.size()); // 3
// need a List back?
List<String> deduped = new ArrayList<>(unique);
System.out.println(deduped.size()); // 3
}
}
这是初学者用到 Set 最常见的原因。只是要知道,这样转一圈会丢失原来的顺序;如果顺序很重要,请用 LinkedHashSet(下文会讲)。
contains、remove 和 size
日常操作和其他集合类似:
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("backend");
tags.add("api");
System.out.println(tags.contains("api")); // true
System.out.println(tags.size()); // 3
tags.remove("backend");
System.out.println(tags.contains("backend")); // false
tags.clear();
System.out.println(tags.isEmpty()); // true
}
}
相比 ArrayList,最大的优势在于 contains。列表必须逐个检查每个元素才能回答(O(n));HashSet 几乎能直接找到答案(大约 O(1))。当你发现自己在循环里调用 list.contains(...) 时,通常就该换成 Set 了。
集合运算:并集、交集、差集
Set 的长处在于组合使用。只要分清每个方法对应哪种运算,读起来就像大白话:
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> a = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> b = new HashSet<>(Set.of(3, 4, 5, 6));
Set<Integer> union = new HashSet<>(a);
union.addAll(b); // everything in either set
System.out.println(union); // 1 2 3 4 5 6
Set<Integer> intersection = new HashSet<>(a);
intersection.retainAll(b); // only what's in both
System.out.println(intersection); // 3 4
Set<Integer> difference = new HashSet<>(a);
difference.removeAll(b); // in a but not b
System.out.println(difference); // 1 2
}
}
关键陷阱:addAll、retainAll 和 removeAll会修改调用它们的那个集合。所以每个例子都先把 a 复制到一个新的 HashSet 里,否则你会毁掉原来的集合。每个结果都要新建一个集合。