集合框架是 Java 開發最高頻工具。本文基於經典教學案例,深度剖析 LinkedList、ArrayList、HashSet 的特有方法、底層數據結構、去重原理與最佳實踐,附帶完整可運行代碼。
LinkedList 實現 List、Deque 接口,底層為雙向鏈表,首尾增刪 O(1),隨機訪問 O(n)。
| JDK 1.6 前 | JDK 1.6+ 替代(推薦) | 語義 | 空集合行為 |
|---|---|---|---|
addFirst/e |
offerFirst/e |
頭部插入 | 無異常 |
getFirst/e |
peekFirst/e |
獲取頭部(不移除) | 前者拋 NoSuchElementException,後者返回 null |
removeFirst/e |
pollFirst/e |
獲取並移除頭部 | 同上 |
// 棧:先進後出 (LIFO) - 只操作頭部
class MyStack<E> {
private LinkedList<E> list = new LinkedList<>();
public void push(E e) { list.addFirst(e); }
public E pop() { return list.removeFirst(); }
public boolean isEmpty() { return list.isEmpty(); }
}
// 隊列:先進先出 (FIFO) - 頭部入,尾部出
class MyQueue<E> {
private LinkedList<E> list = new LinkedList<>();
public void offer(E e) { list.addFirst(e); }
public E poll() { return list.removeLast(); }
public boolean isEmpty() { return list.isEmpty(); }
}
ArrayList 底層為可擴容數組,隨機訪問 O(1),尾部插入均攤 O(1),中間增刪 O(n)。
public static <E> List<E> distinct(List<E> source) {
List<E> result = new ArrayList<>();
for (E e : source) {
if (!result.contains(e)) { // 依賴 equals()
result.add(e);
}
}
return result;
}
關鍵點:contains、remove 底層調用元素 equals(),**自定義對象必須重寫 equals()(通常也要重寫 hashCode())**。
class Person {
private String name;
private int age;
// 構造器、getter...
@Override
public boolean equals(Object o) {
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return this.name.equals(p.name) && this.age == p.age;
}
// 生產環境必須重寫 hashCode(),保持與 equals 契約一致
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
HashSet 底層封裝 HashMap,特點:**無序、不重複、允許一個 null**。
hashCode(),定位桶位置equals() 比較add() 返回 false)@Override
public int hashCode() {
// 讓不同對象盡量分散,減少 equals 調用
return name.hashCode() * 31 + age;
}
HashSet<Person> set = new HashSet<>();
set.add(new Person("Alice", 25));
set.contains(new Person("Alice", 25)); // true - 先算 hashCode 定位,再 equals 比較
set.remove(new Person("Alice", 25)); // true - 同上機制
import java.util.*;
public class CollectionDemo {
public static void main(String[] args) {
// 1. LinkedList 模擬棧
LinkedList<String> stack = new LinkedList<>();
stack.addFirst("A"); stack.addFirst("B"); stack.addFirst("C");
while (!stack.isEmpty()) System.out.println(stack.removeFirst()); // C B A
// 2. ArrayList 去重
List<String> list = Arrays.asList("A", "B", "A", "C", "B");
System.out.println(distinct(list)); // [A, B, C]
// 3. HashSet 存自定義對象
Set<Person> persons = new HashSet<>();
persons.add(new Person("Tom", 20));
persons.add(new Person("Tom", 20)); // 重複,不會添加
persons.add(new Person("Jerry", 20));
persons.forEach(p -> System.out.println(p.getName() + ":" + p.getAge()));
}
public static <E> List<E> distinct(List<E> source) {
List<E> result = new ArrayList<>();
for (E e : source) if (!result.contains(e)) result.add(e);
return result;
}
}
class Person {
private String name; private int age;
public Person(String n, int a) { name=n; age=a; }
public String getName() { return name; }
public int getAge() { return age; }
@Override public boolean equals(Object o) {
if(!(o instanceof Person)) return false;
Person p=(Person)o; return name.equals(p.name) && age==p.age;
}
@Override public int hashCode() { return Objects.hash(name, age); }
}
| 場景 | 推薦集合 | 理由 |
|---|---|---|
| 頻繁隨機訪問、尾部增刪 | ArrayList | 數組底層,get/set O(1) |
| 頻繁首部/中間增刪、實現棧/隊列 | LinkedList | 鏈表底層,首尾增刪 O(1) |
| 去重、集合運算(交/並/差)、無序可接受 | HashSet | 哈希表,add/contains/remove 平均 O(1) |
| 有序去重、範圍查詢 | TreeSet | 紅黑樹,天然有序 |
| 保持插入順序去重 | LinkedHashSet | 鏈表+哈希表,有序且高性能 |
LinkedList 適合首尾操作密集場景,offer/peek/poll 系列方法更安全。ArrayList 去重依賴 equals(),自定義類必須正確重寫 equals/hashCode。HashSet 通過 hashCode 定位 + equals 精確比較保證唯一性,優質 hashCode 是性能關鍵。