首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 集合框架核心對比:List 與 Set 行為差異完整演示

     編輯:Java 綜合教學

Java 集合框架核心對比:List 與 Set 行為差異

List 與 Set 是 Java 集合框架中最基礎的兩大介面,其核心差異在於 有序性 與 唯一性。本文通過程式碼演示與原理分析,徹底釐清兩者行為邊界。

一、核心特性對比表

特性List (ArrayList/LinkedList)Set (HashSet/LinkedHashSet/TreeSet)
元素順序維護插入順序(有序)HashSet 無序,LinkedHashSet 維護插入順序,TreeSet 自然排序
重複元素允許重複不允許重複(唯一性)
索引存取支援 get(index) O(1)/O(n)不支援索引存取
Null 元素允許多個 null最多允許一個 null (HashSet/LinkedHashSet),TreeSet 不允許
底層結構動態陣列/雙向鏈表HashMap/TreeMap
典型實作ArrayList, LinkedList, VectorHashSet, LinkedHashSet, TreeSet

二、程式碼演示:重複元素添加行為

import java.util.*;

public class ListVsSetDemo {
    public static void main(String[] args) {
        // 測試資料:包含重複的 "a"
        String[] items = {"A", "a", "c", "C", "a"};
        
        System.out.println("=== List (ArrayList) 行為 ===");
        List list = new ArrayList<>();
        for (String item : items) {
            boolean added = list.add(item);
            System.out.printf("添加 '%s':%s,當前列表:%s%n", 
                item, added ? "成功" : "失敗", list);
        }
        System.out.println("最終 List 大小:" + list.size());  // 5
        
        System.out.println("\n=== Set (HashSet) 行為 ===");
        Set set = new HashSet<>();
        for (String item : items) {
            boolean added = set.add(item);
            System.out.printf("添加 '%s':%s,當前集合:%s%n", 
                item, added ? "成功" : "失敗", set);
        }
        System.out.println("最終 Set 大小:" + set.size());  // 4
        
        System.out.println("\n=== LinkedHashSet 保持插入順序 ===");
        Set linkedSet = new LinkedHashSet<>();
        linkedSet.addAll(Arrays.asList(items));
        System.out.println("元素順序:" + linkedSet);  // [A, a, c, C]
        
        System.out.println("\n=== TreeSet 自然排序 ===");
        Set treeSet = new TreeSet<>();
        treeSet.addAll(Arrays.asList(items));
        System.out.println("排序順序:" + treeSet);  // [A, C, a, c] (ASCII 碼順序)
    }
}

三、輸出結果分析

=== List (ArrayList) 行為 ===
添加 'A':成功,當前列表:[A]
添加 'a':成功,當前列表:[A, a]
添加 'c':成功,當前列表:[A, a, c]
添加 'C':成功,當前列表:[A, a, c, C]
添加 'a':成功,當前列表:[A, a, c, C, a]
最終 List 大小:5

=== Set (HashSet) 行為 ===
添加 'A':成功,當前集合:[A]
添加 'a':成功,當前集合:[A, a]
添加 'c':成功,當前集合:[A, a, c]
添加 'C':成功,當前集合:[A, a, c, C]
添加 'a':失敗,當前集合:[A, a, c, C]
最終 Set 大小:4

=== LinkedHashSet 保持插入順序 ===
元素順序:[A, a, c, C]

=== TreeSet 自然排序 ===
排序順序:[A, C, a, c]

四、核心機制深度解析

1. List 允許重複的原因

  • 底層陣列/鏈表直接儲存引用,不做唯一性檢查
  • add(E e) 永遠回傳 true,直接在末尾/指定位置插入
  • 時間複雜度:ArrayList 尾部 O(1) 攤還,指定位置 O(n)

2. Set 唯一性保證機制

// HashSet.add() 核心邏輯 (JDK 1.8)
public boolean add(E e) {
    return map.put(e, PRESENT) == null;  // 底層用 HashMap 實現
}

// HashMap.put() 判斷鍵值相等:
// 1. hashCode() 相同
// 2. equals() 回傳 true
// → 視為重複鍵,覆蓋舊值,回傳舊值 → add() 回傳 false

3. 正確覆寫 equals() 與 hashCode() 的關鍵性

public class User {
    private Long id;
    private String name;
    
    // 必須同時覆寫!
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

// 錯誤示範:僅覆寫 equals() 不覆寫 hashCode()
// → 同樣邏輯相等的物件,hashCode 不同 → HashSet 視為不同元素 → 違反 Set 契約

五、常見實作類別選型指南

List 選型

實作適用場景避免場景
ArrayList隨機存取頻繁、尾部新增、記憶體敏感頻繁頭部/中間插刪
LinkedList頻繁頭部/中間插刪、佇列/棧操作隨機存取、記憶體敏感
Vector舊版 API 相容、執行緒安全需求新專案(用 Collections.synchronizedList 或 CopyOnWriteArrayList)

Set 選型

實作適用場景避免場景
HashSet高效能去重、順序無關需保持插入順序、需排序
LinkedHashSet去重 + 保持插入順序(LRU 快取)需自然排序
TreeSet去重 + 自然排序/自定義 Comparator高頻寫入(O(log n))、無序場景
EnumSetEnum 類型專用、位元級效能非 Enum 類型
CopyOnWriteArraySet讀多寫少、執行緒安全遍歷高頻寫入、大資料量

六、效能複雜度速查

操作ArrayListLinkedListHashSetTreeSet
add(尾部)O(1)*O(1)O(1)*O(log n)
add(指定位置)O(n)O(n)N/AN/A
get(index)O(1)O(n)N/AN/A
containsO(n)O(n)O(1)*O(log n)
remove(Object)O(n)O(n)O(1)*O(log n)
遍歷O(n)O(n)O(n)O(n)

* 為攤還/平均複雜度,HashSet 最壞 O(n)(哈希衝突)

七、最佳實踐建議

  • 需求:去重 → new HashSet<>(list) 或 list.stream().distinct().collect(toList())
  • 需求:去重 + 保持順序 → new LinkedHashSet<>(list)
  • 需求:排序 + 去重 → new TreeSet<>(list) 或 list.stream().distinct().sorted().collect(toList())
  • 需求:頻繁 contains 檢查 → 轉為 HashSet 後查詢 O(1)
  • 自訂物件進 Set → 必須 正確覆寫 equals() 與 hashCode()
  • 執行緒安全 → Collections.synchronizedSet(new HashSet<>()) 或 CopyOnWriteArraySet

List 與 Set 的選擇本質上是業務語義的映射:需要順序與重複用 List,需要唯一性用 Set。理解底層 HashMap/Array 實現機制,能避免常見的 equals/hashCode 缺陷導致的業務 Bug。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved