Set 是無序、不重複集合,最多允許一個 null。主流實現:HashSet (哈希表、O(1) 查詢)、LinkedHashSet (維護插入序)、TreeSet (紅黑樹、有序)。本文聚焦 HashSet:底層原理、四大構造器、核心 API、自定義對象正確存入的關鍵 — 重寫 hashCode/equals。
| 特性 | 說明 |
|---|---|
| 底層結構 | HashMap,值統一用 PRESENT 佔位 |
| 去重依據 | hashCode() 定桶位 + equals() 精確比較 |
| 順序性 | 無序,不保證插入/迭代順序 |
| Null 元素 | 允許一個 null |
| 線程安全 | 非線程安全 |
| 擴容機制 | 負載因子 0.75,容量翻倍 (2ⁿ) |
// 1. 默認:容量 16,負載因子 0.75
new HashSet<>();
// 2. 指定初始容量 (避免頻繁擴容)
new HashSet<>(int initialCapacity);
// 3. 指定容量+負載因子
new HashSet<>(int initialCapacity, float loadFactor);
// 4. 從 Collection 複製 (去重)
new HashSet<>(Collection extends E> c);
HashSet set = new HashSet<>();
set.add("張三");
set.add("李四");
set.add("jay");
set.add("jack");
boolean added = set.add("jay"); // false,已存在
System.out.println(set); // [李四, 張三, jay, jack] 順序不固定
System.out.println("大小: " + set.size()); // 4
// 遍歷
for (String s : set) System.out.print(s + " ");
// 判斷包含
System.out.println(set.contains("jay")); // true
// 刪除
set.remove("jack");
class Student {
String name; int age;
public Student(String name, int age) { this.name=name; this.age=age; }
// 缺少 hashCode/equals 重寫!
}
HashSet stuSet = new HashSet<>();
stuSet.add(new Student("張三", 20)); // true
stuSet.add(new Student("張三", 20)); // true ← 應為 false!
System.out.println(stuSet.size()); // 2 ← 預期 1
Object.hashCode() 基於對象地址,兩個 new Student(...) 地址不同 → hashCode 不同 → 落入不同桶 → equals 根本不調用 → 視為不同元素。Object.equals() 比較引用地址,同樣失效。class Student {
String name; int age;
public Student(String name, int age) { this.name=name; this.age=age; }
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override public int hashCode() {
return Objects.hash(name, age); // JDK 7+ 推薦
}
}
// 再次測試
HashSet stuSet = new HashSet<>();
System.out.println(stuSet.add(new Student("張三", 20))); // true
System.out.println(stuSet.add(new Student("張三", 20))); // false ✅
System.out.println(stuSet.size()); // 1 ✅
a.equals(b) == true ⇒ a.hashCode() == b.hashCode() (反向不要求)。a.equals(b) ⇔ b.equals(a)。a.equals(b) && b.equals(c) ⇒ a.equals(c)。a.equals(null) == false。a.equals(a) == true。黃金法則:覆蓋 equals 必須覆蓋 hashCode,且兩者依賴的字段集合完全一致。IDEA
Generate → equals() and hashCode()一鍵生成最靠譜。
Student s = new Student("張三", 20);
set.add(s);
s.setAge(21); // 修改了參與 hashCode/equals 的字段!
System.out.println(set.contains(new Student("張三", 20))); // false ❌
System.out.println(set.contains(s)); // true (同引用)
set.remove(new Student("張三", 20)); // 刪除失敗!
結論:作為 Set 元素 / Map 鍵的對象必須不可變 (或至少在集合存活期間不修改參與比較的字段)。
HashSet = HashMap 鍵集視角,O(1) 去重查詢。LinkedHashSet,需排序用 TreeSet (實現 Comparable/傳 Comparator)。