在 Java 中,Set 是一种不允许存储重复元素的集合接口,它继承自 Collection 接口。Set 通常用于存储唯一元素,如去重场景、数学集合运算等。
Set 接口的核心特性
元素唯一性:不允许存储重复元素,通过 equals() 和 hashCode() 方法判断元素是否重复。
无序性(部分有序):
HashSet:无序(不保证元素顺序)。
LinkedHashSet:按插入顺序排序。
TreeSet:按自然顺序或自定义比较器排序。
常用实现类:
HashSet:基于哈希表实现,性能最优,无序。
LinkedHashSet:继承自 HashSet,使用链表维护插入顺序。
TreeSet:基于红黑树实现,元素有序,不允许 null。
Set 的基本操作
以下是 Set 的常用方法示例:
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
// 创建 HashSet
Set<String> names = new HashSet<>();
// 添加元素
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("Alice"); // 重复元素会被自动过滤
// 检查元素是否存在
System.out.println("是否包含 Bob: " + names.contains("Bob")); // true
// 删除元素
names.remove("Charlie");
System.out.println("删除后的集合: " + names); // [Alice, Bob]
// 遍历集合
System.out.println("遍历集合:");
for (String name : names) {
System.out.println(name);
}
// 使用 Lambda 表达式遍历
names.forEach(name -> System.out.println("Hello, " + name));
// 集合大小
System.out.println("集合大小: " + names.size()); // 2
// 判断集合是否为空
System.out.println("集合是否为空: " + names.isEmpty()); // false
// 清空集合
names.clear();
System.out.println("清空后的集合: " + names); // []
}
}
LinkedHashSet 保持插入顺序
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
public static void main(String[] args) {
// 创建 LinkedHashSet
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("Banana");
linkedSet.add("Apple");
linkedSet.add("Cherry");
// 按插入顺序输出
System.out.println("LinkedHashSet(按插入顺序): " + linkedSet);
// 输出: [Banana, Apple, Cherry]
}
}
TreeSet 自然排序与自定义排序
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args) {
// 自然排序(字符串按字典序)
TreeSet<String> naturalOrder = new TreeSet<>();
naturalOrder.add("Charlie");
naturalOrder.add("Alice");
naturalOrder.add("Bob");
System.out.println("自然排序: " + naturalOrder);
// 输出: [Alice, Bob, Charlie]
// 自定义排序(按字符串长度)
TreeSet<String> lengthOrder = new TreeSet<>((s1, s2) ->
Integer.compare(s1.length(), s2.length())
);
lengthOrder.add("Apple");
lengthOrder.add("Banana");
lengthOrder.add("Kiwi");
System.out.println("按长度排序: " + lengthOrder);
// 输出: [Kiwi, Apple, Banana]
}
}
Set 的集合运算
通过 retainAll()、removeAll() 等方法实现交集、并集、差集:
import java.util.HashSet;
import java.util.Set;
public class SetOperationsExample {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>();
set1.add(1);
set1.add(2);
set1.add(3);
set1.add(4);
Set<Integer> set2 = new HashSet<>();
set2.add(3);
set2.add(4);
set2.add(5);
set2.add(6);
// 交集(set1 ∩ set2)
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("交集: " + intersection); // [3, 4]
// 并集(set1 ∪ set2)
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("并集: " + union); // [1, 2, 3, 4, 5, 6]
// 差集(set1 - set2)
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("差集: " + difference); // [1, 2]
}
}
Set 实现类对比
特性 HashSet LinkedHashSet TreeSet
底层结构 哈希表 哈希表 + 链表 红黑树
元素顺序 无序 按插入顺序 按自然 / 自定义排序
允许 null 是 是 否
时间复杂度 O (1)(基本操作) O (1)(基本操作) O(log n)
线程安全 否 否 否
常见面试问题
HashSet 如何保证元素唯一性?
通过 hashCode() 和 equals() 方法判断元素是否重复。若两个元素 equals() 为 true,则它们的 hashCode() 必须相同。
TreeSet 与 HashSet 的区别?
TreeSet 有序且基于红黑树,HashSet 无序且基于哈希表;TreeSet 不允许 null,HashSet 允许一个 null。
HashSet 与 LinkedHashSet 的性能差异?
两者基本操作时间复杂度均为 O (1),但 LinkedHashSet 因维护链表会稍慢,不过遍历时性能更好。
如何实现线程安全的 Set?
使用 Collections.synchronizedSet(new HashSet<>()) 或 CopyOnWriteArraySet。
使用自定义对象作为 Set 元素
若使用自定义对象,需重写 hashCode() 和 equals() 方法:
import java.util.Objects;
import java.util.Set;
import java.util.HashSet;
class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 hashCode 和 equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class CustomObjectInSet {
public static void main(String[] args) {
Set<Person> people = new HashSet<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
people.add(new Person("Alice", 25)); // 重复元素,不会被添加
System.out.println(people); // [Person{name='Alice', age=25}, Person{name='Bob', age=30}]
}
}
掌握 Set 的核心实现类及其适用场景,能有效解决去重和集合运算等问题。合理重写 hashCode() 和 equals() 是使用自定义对象的关键。