CodeGym /課程 /JAVA 25 SELF /Set:HashSet 與 TreeSet,元素唯一性

Set:HashSet 與 TreeSet,元素唯一性

JAVA 25 SELF
等級 26 , 課堂 3
開放

1. 介紹

先從生活化的例子開始。想像你正在籌辦一場派對並製作賓客名單。你發出邀請後才發現,同一個人被列了兩次(甚至三次——他真的很愛派對!)。如果你使用的是一般的清單(List),這種重複很容易出現。但如果有個集合,會自動不允許將同一位賓客加入兩次——生活就簡單多了。

這時就輪到集合 Set 登場了——「集合」。

Set 是一種只儲存唯一元素的集合。若你嘗試加入已存在的元素,它根本不會被加入(也不會有人介意)。

Set 介面:基本特性

在 Java 中,Set 是一個介面,定義了「無重複」集合的行為。它繼承自 Collection 介面,因此支援新增(add)、刪除(remove)、檢查是否存在(contains)以及遍歷等操作。

關鍵特點:

  • Set 中不會有兩個相同的元素。
  • 元素的儲存順序可能是任意的——這取決於具體實作。
  • 沒有索引:無法像在清單中那樣透過序號存取元素。

宣告語法

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

2. HashSet:快速、簡單、無順序

HashSetSet 介面最常見的實作。它基於雜湊表(和 HashMap 類似,但沒有「鍵-值」配對,只有唯一的值)。最大優勢是操作的速度快——新增、刪除與查找都很快。

HashSet 如何運作?

想像一個收納箱,你把東西放進去。為了快速判斷裡面是否已經有相似的物品,每件物品都會得到自己的「號碼」——雜湊碼。當你把元素加入 HashSet 時,會先計算它的雜湊碼。如果之前沒遇過,就能順利加入集合;如果雜湊碼已存在,還會再透過 equals() 檢查是否相等。只有在物件真的相同時,新元素才不會被加入。

也就是說,HashSet 會自動確保唯一性:兩個相同的物件不會同時出現在其中。

有個重點:如果你使用自訂類別並想把它們存入 HashSet,就必須覆寫 equals()hashCode()。否則集合可能表現得不可預期——明明看起來相同的物件,卻會被視為不同。

HashSet 的基本方法

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

guests.add("伊萬");
guests.add("瑪麗亞");
guests.add("彼得");
guests.add("伊萬"); // 重複!不會加入。

System.out.println(guests); // [伊萬, 瑪麗亞, 彼得] — 順序可能不固定

guests.remove("彼得"); // 刪除元素
System.out.println(guests.contains("瑪麗亞")); // true
System.out.println(guests.size()); // 2

我們來在程式碼中試試看

假設我們的應用程式想要儲存唯一的任務名稱,避免兩個任務同名:

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

public class UniqueTasksDemo {
    public static void main(String[] args) {
        Set<String> tasks = new HashSet<>();
        tasks.add("做 Java 作業");
        tasks.add("摸貓");
        tasks.add("做 Java 作業"); // 重複!

        System.out.println("任務清單:");
        for (String task : tasks) {
            System.out.println("- " + task);
        }
        // 清單中只會有兩個任務,重複的不會加入
    }
}

3. TreeSet:順序很重要!

有時我們不只需要唯一性,還需要已排序的元素集。比如,想按字母順序查看賓客姓名,而不是隨機順序。此時就用 TreeSet

TreeSetSet 的一種實作,會以排序後(遞增)的方式儲存元素。它基於「紅黑樹」結構。

TreeSet 的使用範例

import java.util.Set;
import java.util.TreeSet;

public class SortedGuestsDemo {
    public static void main(String[] args) {
        Set<String> guests = new TreeSet<>();
        guests.add("弗拉基米爾");
        guests.add("阿列克謝");
        guests.add("葉卡捷琳娜");
        guests.add("阿列克謝"); // 重複!

        System.out.println("來賓(按字母順序):");
        for (String guest : guests) {
            System.out.println("- " + guest);
        }
        // 輸出:
        // - 阿列克謝
        // - 弗拉基米爾
        // - 葉卡捷琳娜
    }
}

請注意:如果你加入重複項,它不會出現在集合中。一切如預期!

何時使用 TreeSet?

  • 當需要已排序的唯一元素集時。
  • 當需要快速查找,但對新增速度不那麼敏感(比 HashSet 稍慢)。
  • 如果元素是你的自訂類別,必須是「可比較的」(實作 Comparable),或提供自己的 Comparator

4. 實用細節

HashSet 與 TreeSet:如何選擇?

比較準則 HashSet TreeSet
儲存順序 不保證 依遞增排序
操作速度 較快(O(1)) 較慢(O(log n))
類型要求 任意(只需 equals()/hashCode() ComparableComparator
典型情境 需要快速存取唯一元素時 需要有序輸出/遍歷時

使用 Set 的特性

  • 沒有索引。List 不同,Set 沒有 get(int index) 方法。若需要透過索引存取,請用 List
  • 沒有重複。 若你嘗試加入已存在的元素,它不會加入。方法 add 會回傳 false
  • 不保證順序(TreeSet 例外)。HashSet 中,元素順序每次執行都可能不同。若需要維持插入順序,請用 LinkedHashSet
  • Null 值。
    • HashSet 允許儲存一個 null 元素。
    • TreeSet 在沒有特別的 Comparator 時不允許加入 null,否則會拋出 NullPointerException

5. Set 的典型任務

從列表移除重複項

假設有一個學生清單,其中有人被記錄了兩次。我們需要只保留唯一的姓名:

import java.util.*;

public class RemoveDuplicatesDemo {
    public static void main(String[] args) {
        List<String> students = Arrays.asList("安娜", "伊戈爾", "安娜", "瑪麗亞", "伊戈爾", "帕維爾");

        Set<String> uniqueStudents = new HashSet<>(students);

        System.out.println("唯一的學生: " + uniqueStudents);
        // 順序不保證!
    }
}

如果需要排序後的結果,請使用 TreeSet

Set<String> sortedUniqueStudents = new TreeSet<>(students);
System.out.println("唯一的學生(按字母順序): " + sortedUniqueStudents);

檢查唯一性(例如使用者登入名)

Set<String> usedLogins = new HashSet<>();
usedLogins.add("student1");
usedLogins.add("java_lover");

String newLogin = "student1";
if (usedLogins.contains(newLogin)) {
    System.out.println("此登入名已被佔用!");
} else {
    System.out.println("登入名可用!");
}

遍歷集合元素

可使用 for-each 迴圈進行遍歷:

for (String name : uniqueStudents) {
    System.out.println(name);
}

6. 使用 Set 的常見錯誤

錯誤 №1:期望 HashSet 中有特定的元素順序。 許多新手會納悶,為什麼集合中的元素輸出順序「怪怪的」。這很正常——HashSet 不保證順序。若需要維持插入順序,請使用 LinkedHashSet;若需要排序,請用 TreeSet

錯誤 №2:試圖透過索引存取元素。 有時會寫出像 set.get(0) 之類的程式碼。這是不行的:Set 不支援索引。若需要索引式存取,請用 List

錯誤 №3:儲存可變物件。 如果你儲存的物件,其參與 equals()/hashCode() 的欄位可能會改變,那麼在那些欄位變動後,該元素可能會對集合來說「找不到」。請讓元素不可變,或不要變更識別相關的欄位。

錯誤 №4:以為重複元素會被加入。 多次加入同一個元素不會讓集合的大小增加——重複會被忽略,方法 add 會回傳 false

錯誤 №5:使用基本型別。Set<int> 這樣的寫法無法編譯。請使用包裝類別:Set<Integer>Set<Double> 等。

1
任務
JAVA 25 SELF, 等級 26, 課堂 3
上鎖
按字母排序的活動參與者名單 🤵‍♀️🤵‍♂️
按字母排序的活動參與者名單 🤵‍♀️🤵‍♂️
1
任務
JAVA 25 SELF, 等級 26, 課堂 3
上鎖
購物清單優化 🛒✨
購物清單優化 🛒✨
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION