Java HashSet

HashSet 是最常用的 Set 實作,基於 HashMap,元素不重複且無序。

建立 HashSet

import java.util.HashSet;
import java.util.Set;

// 空的 HashSet
Set<String> set1 = new HashSet<>();

// 從集合建立
Set<String> set2 = new HashSet<>(Arrays.asList("A", "B", "C"));

// Java 9+ 建立不可變 Set
Set<String> set3 = Set.of("A", "B", "C");

常用方法

新增元素

Set<String> set = new HashSet<>();

set.add("Apple");      // true
set.add("Banana");     // true
set.add("Apple");      // false(已存在,不會重複加入)

set.addAll(Arrays.asList("C", "D"));  // 加入多個

移除元素

Set<String> set = new HashSet<>(Arrays.asList("A", "B", "C"));

set.remove("A");       // 移除元素
set.removeAll(Arrays.asList("B", "C"));  // 移除多個
set.removeIf(s -> s.length() > 3);       // 條件移除
set.clear();           // 清空

檢查元素

Set<String> set = new HashSet<>(Arrays.asList("A", "B", "C"));

boolean has = set.contains("A");     // true
boolean empty = set.isEmpty();        // false
int size = set.size();                // 3

遍歷

Set<String> set = new HashSet<>(Arrays.asList("A", "B", "C"));

// for-each
for (String s : set) {
    System.out.println(s);
}

// forEach + Lambda
set.forEach(System.out::println);

// Iterator
Iterator<String> it = set.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

集合運算

Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4));
Set<Integer> set2 = new HashSet<>(Arrays.asList(3, 4, 5, 6));

// 聯集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);  // [1, 2, 3, 4, 5, 6]

// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);  // [3, 4]

// 差集
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);  // [1, 2]

實際應用

去除重複

String[] words = {"apple", "banana", "apple", "cherry", "banana"};

// 轉為 Set 去重
Set<String> uniqueWords = new HashSet<>(Arrays.asList(words));
// [apple, banana, cherry]

// 再轉回 List
List<String> uniqueList = new ArrayList<>(uniqueWords);

快速查找

Set<String> validCodes = new HashSet<>(Arrays.asList("A001", "A002", "B001"));

String input = "A001";
if (validCodes.contains(input)) {
    System.out.println("有效的代碼");
}

找出共同元素

List<String> list1 = Arrays.asList("A", "B", "C", "D");
List<String> list2 = Arrays.asList("C", "D", "E", "F");

Set<String> set1 = new HashSet<>(list1);
set1.retainAll(list2);
System.out.println(set1);  // [C, D]

檢查是否有重複

public static <T> boolean hasDuplicates(List<T> list) {
    Set<T> set = new HashSet<>(list);
    return set.size() < list.size();
}

HashSet vs TreeSet vs LinkedHashSet

特性HashSetTreeSetLinkedHashSet
順序排序插入順序
效能O(1)O(log n)O(1)
null允許一個不允許允許一個
// 需要排序
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
// [1, 2, 3]

// 需要保持插入順序
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("C");
linkedSet.add("A");
linkedSet.add("B");
// [C, A, B]

注意事項

hashCode 和 equals

元素物件必須正確實作 hashCode()equals()

public class Person {
    private String name;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

可變物件

不建議將可變物件作為 Set 的元素,修改後可能無法正確查找。

與陣列轉換

// Set 轉陣列
Set<String> set = new HashSet<>(Arrays.asList("A", "B", "C"));
String[] array = set.toArray(new String[0]);

// 陣列轉 Set
String[] arr = {"X", "Y", "Z"};
Set<String> fromArray = new HashSet<>(Arrays.asList(arr));