Java Collections: interface, implementation và cách chọn
Chọn collection từ semantics trước: có duplicate không, cần ordering hay sorted order, truy cập theo key/index, queue policy và concurrency ra sao. Complexity là bước kiểm tra tiếp theo.
SequencedCollection, SequencedSet và SequencedMap xuất hiện từ Java 21; ví dụ Java bên dưới không cần preview feature. [R04]
Các phần “Bổ sung” tách kiến thức kiểm chứng từ API với khuyến nghị thiết kế. Contract là điều API cam kết; trade-off phải được đo trên workload; scope chỉ rõ operation, object hoặc thread nào được bảo vệ.
1. Sơ đồ interface
Bản đồ các họ collection. Sơ đồ dưới giữ đủ interface và implementation của tài liệu nguồn. Đây là cách nhóm để học, không phải danh sách đầy đủ các quan hệ extends/implements; phần Java 21 ngay sau sơ đồ bổ sung các superinterface còn thiếu. [R04] [R01]
Iterable<E>
└── Collection<E>
├── List<E>
│ ├── ArrayList
│ ├── LinkedList
│ └── CopyOnWriteArrayList
├── Set<E>
│ ├── HashSet
│ ├── LinkedHashSet
│ ├── SortedSet / NavigableSet
│ │ ├── TreeSet
│ │ └── ConcurrentSkipListSet
│ └── EnumSet
└── Queue<E>
├── Deque<E>
│ ├── ArrayDeque
│ ├── LinkedList
│ └── ConcurrentLinkedDeque
├── PriorityQueue
└── BlockingQueue
├── ArrayBlockingQueue
├── LinkedBlockingQueue
├── PriorityBlockingQueue
└── SynchronousQueue
Map<K,V> (không extends Collection)
├── HashMap / LinkedHashMap / WeakHashMap / IdentityHashMap
├── SortedMap / NavigableMap → TreeMap / ConcurrentSkipListMap
├── EnumMap
└── ConcurrentMap → ConcurrentHashMap
Map thuộc Collections Framework nhưng không extends Collection, vì nó lưu mapping key–value thay vì một tập phần tử đơn.1.1. Bổ sung · đọc contract trước tên class
| Họ | Điều cần xác định | Không được suy ra |
|---|---|---|
List | Sequence có index; các implementation phổ biến cho phép duplicate. [R06] | Có index không có nghĩa get(i) luôn O(1). |
Set | Không lặp phần tử theo equality contract; chọn thêm yêu cầu encounter order hoặc comparator. [R10] [R13] | Unique không có nghĩa sorted. |
Map | Mỗi key tối đa một mapping; nhiều key có thể cùng value. [R02] | Không kế thừa Collection; có thể thao tác qua collection views. |
Queue / Deque | Chính sách lấy head và thao tác ở một/hai đầu. [R25] [R26] | Queue không mặc định FIFO, bounded hoặc thread-safe. |
1.2. Bổ sung · hierarchy có encounter order từ Java 21
Contract Java 21: List là SequencedCollection; Deque đồng thời là Queue và SequencedCollection. SequencedSet nối Set với SequencedCollection, còn SequencedMap vẫn ở nhánh Map riêng. [R04] [R06] [R26]
Quan hệ is-a, đọc trái sang phải là: subtype → supertype
List → SequencedCollection → Collection → Iterable
Deque → Queue → Collection → Iterable
Deque → SequencedCollection
SequencedSet → Set + SequencedCollection
SortedSet → SequencedSet
NavigableSet → SortedSet
SequencedMap → Map
SortedMap → SequencedMap
NavigableMap → SortedMap
LinkedHashSet → SequencedSet
LinkedHashMap → SequencedMap
Ordered là có encounter order; insertion order theo thứ tự chèn; access order thay đổi theo truy cập; sorted dựa natural order/comparator. Không đồng nhất các khái niệm này. reversed() cung cấp reverse-ordered view; mutation là optional, và sorted collections không cho tự đặt vị trí bằng addFirst/addLast. [R04] [R05] [R17]
Ví dụ chạy được · SequencedCheck.java
Quan sát reverse view của một ArrayList: đổi phần tử đầu của view làm đổi phần tử cuối list gốc. Kết quả cần thấy: PASS: reversed view.
import java.util.ArrayList;
import java.util.List;
public class SequencedCheck {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("A", "B", "C"));
List<String> reversed = original.reversed(); // Java 21
if (!reversed.equals(List.of("C", "B", "A"))) {
throw new AssertionError("Unexpected encounter order");
}
reversed.set(0, "Z"); // View writes through to this ArrayList
if (!original.equals(List.of("A", "B", "Z"))) {
throw new AssertionError("Expected a backed view");
}
System.out.println("PASS: reversed view");
}
}Chạy với JDK 21: javac --release 21 SequencedCheck.java rồi java SequencedCheck.
2. List implementations
| Implementation | Cấu trúc | Điểm mạnh | Điểm yếu |
|---|---|---|---|
| ArrayList | Mảng động contiguous | Get theo index O(1), append amortized O(1), locality tốt. | Insert/remove giữa O(n), resize phải copy. |
| LinkedList | Doubly-linked nodes | Thêm/xóa O(1) khi đã có node/iterator; implement Deque. | Get/search O(n), allocation và locality kém; hiếm khi tốt hơn ArrayList. |
| CopyOnWriteArrayList | Copy toàn array khi write | Iteration snapshot, read không lock; hợp listener list ít đổi. | Write O(n), memory spike; không hợp mutation thường xuyên. |
2.1. Bổ sung · complexity phải gồm chi phí tìm vị trí
Làm rõ mô hình: “contiguous” trong bảng là các slot của backing array; không có cam kết rằng mọi object phần tử nằm cạnh nhau. ArrayList append có chi phí amortized O(1), không phải mọi lần append đều O(1); resize vẫn có thể copy O(n). Capacity/growth policy cụ thể không phải contract của List. [R07] [R06]
LinkedList chỉ có lợi thế nối/gỡ node O(1) sau khi iterator đã ở đúng vị trí; API không đưa raw node cho caller. Gọi listIterator(i) hoặc remove(i) còn có traversal. Vì vậy, “nhiều insert/remove” chưa đủ để chọn linked list. Kết luận “hiếm khi nhanh hơn” trong bảng là heuristic cần benchmark, không là guarantee. [R08]
2.2. Bổ sung · CopyOnWriteArrayList không đóng băng object
Iterator giữ snapshot backing array tại lúc tạo; các write sau đó không đổi tập reference nó đang duyệt. Nhưng object được các reference trỏ tới vẫn có thể mutable. Iterator này không hỗ trợ remove; list iterator cũng không hỗ trợ set/add. [R09]
Trade-off: phù hợp listener/registry nhỏ, ít thay đổi; mỗi write có hiệu lực có thể copy array, và iterator sống lâu có thể giữ snapshot cũ. Đây không phải lựa chọn mặc định cho write-heavy workload, cũng không tự làm phần tử mutable thành thread-safe. [R09]
3. Set implementations
- HashSet: backed by
HashMap; uniqueness theoequals/hashCode, average O(1), không đảm bảo order. - LinkedHashSet: thêm linked order, giữ insertion order với memory cost cao hơn.
- TreeSet: red-black tree, sorted/navigable O(log n); equality thực dụng dựa comparison trả 0 nên comparator phải consistent với equals nếu cần Set contract trực giác.
- EnumSet: bit-vector tối ưu cho enum, compact và nhanh; không nhận null.
3.1. Bổ sung · “trùng” phải khớp business identity
Contract: hai object equals nhau phải có cùng hash code. Tránh đổi field tham gia equals/hashCode khi object còn trong hash set hoặc đang làm map key; lookup/remove có thể không còn đúng mong đợi. Với sorted set, tránh đổi field comparator đang dùng. [R10] [R02]
Hiệu chỉnh cách đọc TreeSet: compare(a,b)==0 khiến tree coi chúng là cùng phần tử. Comparator consistent với equals là điều kiện để tuân thủ general Set contract, không chỉ để kết quả “trực giác”. TreeSet được backing bởi TreeMap; red-black tree nằm trong implementation đó. [R13] [R18]
Ví dụ chạy được · SetEqualityCheck.java
BigDecimal minh họa hai định nghĩa equality: equals xét cả scale, còn compareTo so giá trị số. Kết quả: PASS: HashSet=2, TreeSet=1; đây là khác biệt semantics, không phải mất dữ liệu ngẫu nhiên. [R15]
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
public class SetEqualityCheck {
public static void main(String[] args) {
var a = new BigDecimal("1.0");
var b = new BigDecimal("1.00");
var input = List.of(a, b);
int byEquals = new HashSet<>(input).size();
int byComparison = new TreeSet<>(input).size();
if (a.equals(b) || a.compareTo(b) != 0
|| byEquals != 2 || byComparison != 1) {
throw new AssertionError("Unexpected equality semantics");
}
System.out.println("PASS: HashSet=2, TreeSet=1");
}
}Chạy với JDK 21: javac --release 21 SetEqualityCheck.java rồi java SetEqualityCheck.
3.2. Bổ sung · order, null và concurrent sorted set
HashSet nhận một phần tử null; O(1) trung bình giả định hash phân bố tốt. LinkedHashSet giữ insertion order thông thường, nhưng Java 21 có thao tác đặt lại vị trí rõ ràng. EnumSet duyệt theo thứ tự khai báo enum, không theo thứ tự add, và cấm null. [R11] [R12] [R14]
ConcurrentSkipListSet dành cho sorted/range access dùng chung giữa threads: expected O(log n) cho thao tác chính, weakly consistent iterator, không nhận null. Đổi từ TreeSet sang loại concurrent này không tạo ra transaction nhiều thao tác. [R24]
4. Map implementations
| Map | Semantics | Use case |
|---|---|---|
| HashMap | Average O(1), một null key, không thread-safe/order. | Lookup tổng quát. |
| LinkedHashMap | Insertion hoặc access order. | Ordered map, simple bounded LRU qua removeEldestEntry. |
| TreeMap | Sorted/Navigable, O(log n). | Range, floor/ceiling, ordered traversal. |
| EnumMap | Enum keys, array-like internal representation. | Mapping theo enum domain. |
| WeakHashMap | Weak keys có thể mất khi không còn strong reference. | Metadata/cache phụ thuộc lifecycle key; không phải general cache. |
| IdentityHashMap | So key bằng ==, không phải equals. | Object graph/identity algorithms chuyên biệt. |
| ConcurrentHashMap | Concurrent, weakly consistent iteration, không null. | Shared map; compound invariant dùng atomic APIs. |
4.1. Bổ sung · order, lifecycle và các map chuyên biệt
HashMap cho phép null values và một null key; get(key)==null chưa phân biệt được “không có key” với “value là null”. Iteration có chi phí theo capacity + size, nên cấp capacity quá lớn không miễn phí. [R16] [R02]
LinkedHashMap mặc định insertion order; constructor với accessOrder=true dùng access order. removeEldestEntry cho phép LRU giới hạn đơn giản, nhưng không tự bổ sung TTL hay concurrency policy. Trong access-order map, get có thể đổi encounter order và được xem là structural modification. [R17]
| Map | Contract / giới hạn cần nhớ |
|---|---|
TreeMap | Natural ordering thường không nhận null key; comparator tùy chỉnh có thể hỗ trợ null. Null values được phép. Comparator phải consistent với equals để giữ general Map contract. [R18] |
EnumMap | Keys cùng một enum type; duyệt theo thứ tự khai báo enum; cấm null key nhưng cho phép null value. [R19] |
WeakHashMap | GC có thể làm entry biến mất mà ứng dụng không gọi remove; không cam kết thời điểm. Value được giữ mạnh: value trỏ lại key có thể giữ key sống, làm kỳ vọng giải phóng thất bại. Không thay thế eviction/TTL policy. [R20] |
IdentityHashMap | Reference equality == áp dụng cho keys và values; cố ý khác general Map contract. Chỉ dùng khi domain thật sự là object identity, như duyệt graph/copy graph. [R21] |
ConcurrentSkipListMap | Kết hợp concurrent access với sorted/range queries; expected O(log n), weak iteration, không null keys/values. Đây là nhánh bổ sung khi vừa cần concurrency vừa cần range. [R23] |
4.2. Bổ sung · atomic theo key, không atomic toàn nghiệp vụ
ConcurrentHashMap cấm cả null key và null value. Dùng putIfAbsent, compute hoặc merge thay cho chuỗi check-then-act không được bảo vệ. Atomicity của những API này không biến nhiều key hoặc thao tác database thành một transaction. Callback cần ngắn, không sửa mapping khác của chính map, và không làm I/O chậm trong critical update. [R22]
Ví dụ chạy được · ConcurrentCounterCheck.java
Bốn worker cùng tăng một key bằng merge. Future.get() đợi hoàn tất và đưa lỗi worker về main. Kết quả 4000 là số increment được lập trình, không phải benchmark throughput.
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ConcurrentCounterCheck {
public static void main(String[] args) throws Exception {
var counts = new ConcurrentHashMap<String, Integer>();
var tasks = new ArrayList<Future<?>>();
try (var pool = Executors.newFixedThreadPool(4)) {
for (int worker = 0; worker < 4; worker++) {
tasks.add(pool.submit(() -> {
for (int i = 0; i < 1_000; i++) {
counts.merge("events", 1, Integer::sum);
}
}));
}
for (var task : tasks) {
task.get(); // Wait, and propagate worker failures
}
}
if (!Integer.valueOf(4_000).equals(counts.get("events"))) {
throw new AssertionError("Lost updates");
}
System.out.println("PASS: per-key merge=4000");
}
}Chạy với JDK 21: javac --release 21 ConcurrentCounterCheck.java rồi java ConcurrentCounterCheck.
ConcurrentHashMap<K, ArrayList<V>> chỉ bảo vệ thao tác map, không bảo vệ các lần add trên cùng list value. Invariant liên quan nhiều key hoặc cả value cần thiết kế đồng bộ riêng. Kết quả size()/duyệt khi đang có write cũng không phải snapshot toàn map. [R22]5. Queue và Deque
Queue cung cấp hai nhóm API: add/remove/element ném exception khi thất bại; offer/poll/peek trả special value. Deque thao tác hai đầu và thường thay cả queue lẫn stack; ưu tiên ArrayDeque thay Stack.
- PriorityQueue: heap; chỉ phần tử head là nhỏ/lớn nhất theo comparator, iteration không sorted.
- ArrayBlockingQueue: bounded array, capacity cố định; backpressure rõ.
- LinkedBlockingQueue: optionally bounded; mặc định capacity rất lớn nên cần cấu hình.
- SynchronousQueue: không giữ element; producer handoff trực tiếp cho consumer.
- ConcurrentLinkedQueue: non-blocking unbounded queue; không tự có backpressure.
5.1. Bổ sung · chọn API khi đầy và khi rỗng
| Thao tác | Exception | Trả ngay | Chờ trong BlockingQueue |
|---|---|---|---|
| Thêm phần tử | add(e): capacity đầy → IllegalStateException | offer(e): đầy → false | put(e); hoặc offer(e, timeout, unit) |
| Lấy và xóa head | remove(): rỗng → NoSuchElementException | poll(): rỗng → null | take(); hoặc poll(timeout, unit) |
| Chỉ xem head | element(): rỗng → NoSuchElementException | peek(): rỗng → null | Không có blocking-peek chung |
Không phải “offer không bao giờ ném exception”: null/type/value không hợp lệ vẫn có thể gây exception. null được dùng làm sentinel “rỗng”, nên tránh null ngay cả khi một implementation như LinkedList cho phép. [R25] [R29]
Với Deque, FIFO thường là addLast/offerLast + removeFirst/pollFirst; LIFO là push + pop ở đầu. ArrayDeque có nhiều thao tác hai đầu amortized O(1), không null và không thread-safe. ConcurrentLinkedDeque là lựa chọn concurrent unbounded, không tạo capacity backpressure. [R26] [R27] [R35]
5.2. Bổ sung · priority không phải sorted iteration
Head của PriorityQueue là phần tử nhỏ nhất theo comparator; đảo comparator để lấy giá trị lớn nhất trước. Các phần tử bằng priority không được bảo đảm FIFO. offer/poll O(log n), peek O(1); contains/remove(Object) O(n). Muốn lấy theo priority, drain bằng poll/remove, không dùng thứ tự iterator. [R28]
PriorityBlockingQueue logically unbounded: take() có thể đợi dữ liệu, còn put() không đợi capacity. LinkedBlockingQueue() mặc định có capacity Integer.MAX_VALUE, không có nghĩa bộ nhớ thực đủ chứa ngần ấy phần tử. [R32] [R31]SynchronousQueue không có storage capacity: offer chỉ thành công khi có consumer sẵn sàng nhận. ConcurrentLinkedQueue là unbounded FIFO, nên application phải giới hạn admission khi producer nhanh hơn consumer. ArrayBlockingQueue có thể bật fairness cho các thread đang chờ, đổi lại thường giảm throughput; fairness không phải cam kết latency tuyệt đối. [R33] [R34] [R30]
Ví dụ chạy được · QueuePolicyCheck.java
Queue capacity 2 được làm đầy khi chưa chạy consumer. Phần tử thứ ba phải bị từ chối sau timed offer; sau đó FIFO và priority-drain được kiểm tra riêng. Không kiểm chứng elapsed time đúng 10 ms vì scheduler có thể làm thời gian thực dài hơn.
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class QueuePolicyCheck {
public static void main(String[] args) throws InterruptedException {
var inbox = new ArrayBlockingQueue<String>(2);
if (!inbox.offer("A") || !inbox.offer("B")) {
throw new AssertionError("Expected two accepted elements");
}
// No consumer is running: the queue stays full.
if (inbox.offer("C", 10, TimeUnit.MILLISECONDS)) {
throw new AssertionError("Expected explicit overload rejection");
}
if (!"A".equals(inbox.take()) || !"B".equals(inbox.take())
|| inbox.poll() != null) {
throw new AssertionError("Unexpected FIFO / empty behavior");
}
var priority = new PriorityQueue<>(List.of(3, 1, 2));
var drained = new ArrayList<Integer>();
while (!priority.isEmpty()) {
drained.add(priority.remove()); // Not iterator traversal
}
if (!drained.equals(List.of(1, 2, 3))) {
throw new AssertionError("Unexpected priority order");
}
System.out.println("PASS: bounded rejection, FIFO, priority drain");
}
}Chạy với JDK 21: javac --release 21 QueuePolicyCheck.java rồi java QueuePolicyCheck.
5.3. Bổ sung · backpressure là policy của toàn flow
Khuyến nghị thiết kế: chọn capacity từ bộ nhớ và thời gian chờ cho phép; định nghĩa rõ overload sẽ block, timeout, reject hay drop có ghi nhận. Đo queue depth, oldest-item age, timeout/rejection rate và thời gian xử lý. Đừng busy-retry ngay khi offer trả false, vì như vậy làm mất tác dụng giới hạn tải.
Scope: enqueue thành công không có nghĩa business processing đã xong hoặc dữ liệu bền qua process crash. BlockingQueue cung cấp happens-before từ hành động trước khi put sang hành động sau khi thread khác lấy phần tử, không bảo vệ mọi mutation sau handoff. API cũng không có cơ chế close/shutdown chung; cần quy ước interruption, drain hoặc tín hiệu kết thúc. [R29]
6. Views, iteration và mutability
map.keySet(),values(),entrySet()thường là backed views; thay đổi view có thể đổi map.subList()là view của list gốc; structural modification không phối hợp có thể gây lỗi.- Iterator collection thường fail-fast theo best effort, không phải synchronization guarantee.
List.of/Set.of/Map.oftạo unmodifiable collection và không nhận null.Collections.unmodifiableListlà read-only view; backing list vẫn có thể đổi. Defensive copy dùngList.copyOfkhi cần snapshot nông.
6.1. Bổ sung · backed view là chia sẻ state, không phải bản sao
Làm rõ “thường là” trong nguồn: Map.keySet(), values() và entrySet() có backed-view contract. Với mutable map hỗ trợ thao tác đó, xóa qua view xóa mapping; không suy ra view hỗ trợ add. Unmodifiable map vẫn không cho sửa qua view. [R02]
subList(from, to) lấy khoảng [from, to). Structural modification trực tiếp ở backing list, ngoài đường view, làm semantics của sublist undefined theo contract; không chỉ đơn giản “chắc chắn ném ConcurrentModificationException”. Muốn tách vòng đời và cấu trúc, tạo copy dưới ownership/lock phù hợp. [R06]
| Cách tạo | Mutation qua kết quả | Quan hệ với dữ liệu đầu vào |
|---|---|---|
Arrays.asList(array) | Fixed-size: hỗ trợ set, không add/remove đổi size | Backed bởi array; thay slot phản chiếu hai phía. [R37] |
Collections.unmodifiableList(list) | Không add/remove/set qua view | Backing list vẫn sửa được qua reference khác. [R36] |
List.of(...) | Unmodifiable; không null | Không deep-copy object phần tử; duplicate được phép. [R06] |
Set.of(...) / Map.of(...) | Unmodifiable; không null | Set.of từ chối phần tử trùng; Map.of từ chối key trùng; không hứa insertion order. [R10] [R02] |
List.copyOf(collection) | Unmodifiable; không null | Snapshot nông về membership/order, không deep clone; implementation có thể tái sử dụng instance đủ điều kiện. [R06] [R38] |
new ArrayList<>(collection) | Mutable | Cấu trúc list độc lập, nhưng vẫn chia sẻ reference tới phần tử. [R07] |
Version caveat: các factory of của List/Set/Map có từ Java 9; copyOf có từ Java 10. “Unmodifiable” nói về API của container, không khẳng định element graph bất biến. Lấy copy trong khi source bị sửa đồng thời cũng không tự tạo snapshot nguyên tử của toàn state. [R06] [R10] [R02] [R38]
Ví dụ chạy được · ViewCopyCheck.java
So sánh một read-only view với shallow copy, sau đó quan sát mutable element, subList, keySet và fixed-size array view. Kết quả cần thấy: PASS: backed views and shallow copy.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
public class ViewCopyCheck {
public static void main(String[] args) {
var backing = new ArrayList<>(List.of("A", "B"));
var view = Collections.unmodifiableList(backing);
var copy = List.copyOf(backing);
backing.set(0, "Z");
if (!view.get(0).equals("Z") || !copy.get(0).equals("A")) {
throw new AssertionError("View and copy must differ");
}
var mutableElement = new StringBuilder("A");
var shallow = List.copyOf(List.of(mutableElement));
mutableElement.append("!");
if (!shallow.get(0).toString().equals("A!")) {
throw new AssertionError("Copy is shallow, not a deep clone");
}
var range = backing.subList(0, 1);
range.clear(); // Structural edit through the view is supported here
if (!backing.equals(List.of("B"))) {
throw new AssertionError("subList must affect the backing list");
}
var map = new HashMap<>(java.util.Map.of("A", 1, "B", 2));
map.keySet().remove("A");
if (map.containsKey("A")) {
throw new AssertionError("keySet is backed by the map");
}
String[] array = {"A", "B"};
var fixedSize = Arrays.asList(array);
fixedSize.set(0, "Z");
if (!array[0].equals("Z")) {
throw new AssertionError("Arrays.asList must write through");
}
System.out.println("PASS: backed views and shallow copy");
}
}Chạy với JDK 21: javac --release 21 ViewCopyCheck.java rồi java ViewCopyCheck.
6.2. Bổ sung · ba kiểu iterator, ba giới hạn khác nhau
| Kiểu | Ví dụ | Quan sát / giới hạn |
|---|---|---|
| Fail-fast, best effort | ArrayList, HashMap | Có thể phát hiện structural modification sai đường và ném CME; có thể xảy ra ngay trong một thread. Không được phụ thuộc CME cho correctness. [R07] [R16] |
| Snapshot | CopyOnWriteArrayList | Tập reference tại lúc tạo iterator; không thấy write sau đó, không cho iterator.remove; không deep snapshot phần tử. [R09] |
| Weakly consistent | ConcurrentHashMap, concurrent skip lists/queues | Duyệt được khi cập nhật; có thể thấy một phần update. Không CME do concurrent update, nhưng không phải ảnh chụp toàn collection ở một thời điểm. [R22] [R23] [R34] |
Cách sửa an toàn có scope: với mutable list thông thường, dùng Iterator.remove() của chính iterator đang duyệt hoặc removeIf trong quyền truy cập phù hợp. Với Collections.synchronizedList, traversal vẫn phải đặt trong synchronized(wrapper), và mọi đường truy cập phải dùng cùng wrapper; không khóa một view khác hoặc lộ backing list ra ngoài. [R01] [R36]
Ví dụ chạy được · IteratorModesCheck.java
Kiểm tra xóa bằng iterator hợp lệ, snapshot của COW và weak iteration. Cố ý không assert key B có xuất hiện trong weak iterator; việc chạy ra một kết quả không biến nó thành guarantee.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class IteratorModesCheck {
public static void main(String[] args) {
var ordinary = new ArrayList<>(List.of(1, 2, 3));
var editable = ordinary.iterator();
while (editable.hasNext()) {
if (editable.next() == 2) {
editable.remove(); // Mutate via this iterator
}
}
if (!ordinary.equals(List.of(1, 3))) {
throw new AssertionError("Iterator removal failed");
}
var listeners = new CopyOnWriteArrayList<>(List.of("A", "B"));
var snapshot = listeners.iterator();
listeners.add("C");
var seen = new ArrayList<String>();
snapshot.forEachRemaining(seen::add);
if (!seen.equals(List.of("A", "B"))) {
throw new AssertionError("Expected the old snapshot");
}
var shared = new ConcurrentHashMap<String, Integer>();
shared.put("A", 1);
var weak = shared.keySet().iterator();
shared.put("B", 2);
weak.forEachRemaining(key -> {
if (!key.equals("A") && !key.equals("B")) {
throw new AssertionError("Unknown key");
}
});
// Deliberately do not assert whether B was observed.
System.out.println("PASS: iterator removal, snapshot, weak iteration");
}
}Chạy với JDK 21: javac --release 21 IteratorModesCheck.java rồi java IteratorModesCheck.
7. Cách chọn nhanh
Cần key → Map
cần sorted/range → TreeMap
cần insertion/access order → LinkedHashMap
shared concurrent → ConcurrentHashMap
còn lại → HashMap
Cần collection phần tử
cần duplicate + index → ArrayList
cần unique → HashSet
cần order → LinkedHashSet
cần sorted/range → TreeSet
cần FIFO/LIFO hai đầu → ArrayDeque
cần priority → PriorityQueue
producer-consumer/backpressure → bounded BlockingQueue
7.1. Bổ sung · các nhánh trong sơ đồ không loại trừ nhau
Cách dùng: sơ đồ nguồn là điểm bắt đầu, không phải chuỗi if/else ưu tiên điều kiện đầu tiên. Hãy xét đồng thời identity, order, access pattern, ownership, mutability và boundedness rồi mới cân complexity.
| Nhu cầu | Ứng viên | Bằng chứng trước khi chốt |
|---|---|---|
| Shared + sorted/range | ConcurrentSkipListMap/Set [R23] [R24] | Kiểm tra range semantics, concurrent updates và snapshot requirement. |
| Domain là enum | EnumSet / EnumMap [R14] [R19] | Domain cố định; null policy, thứ tự khai báo đáp ứng yêu cầu. |
| Read-mostly listener list | CopyOnWriteArrayList [R09] | Đo write cost, số listener, thời gian giữ iterator và allocation. |
| Priority + concurrent + giới hạn memory | PriorityBlockingQueue kèm admission policy riêng [R32] | Không nhầm constructor initial capacity thành hard bound; test overload và starvation. |
| Shared + insertion/access order | Thiết kế khóa/ownership bao quanh ordered map [R17] [R36] | Lock phải bao phủ compound action; access-order get có thể sửa order. |
| Trả dữ liệu ra API không cho caller sửa container | Unmodifiable shallow copy phù hợp [R38] | Quy định riêng mutable elements, copy timing và ownership. |
7.2. Bổ sung · phiếu quyết định cho một workload
Bài tự kiểm tra đề xuất: chọn một luồng đang dùng collection và ghi một đoạn quyết định gồm: identity/duplicates; encounter order hay sorted/range; read/write ratio; thread ownership; null policy; capacity; iterator/copy semantics. Nêu một ứng viên bị loại và lý do. Không xem đây là lab nguồn hay tăng số lab của Part 01.
Evidence nên có: test identity/comparator, test aliasing view/copy, test update concurrent, test queue đầy, và measurement allocation/latency trên dữ liệu đại diện. Thông số máy, JDK, workload và cách đo phải đi kèm kết quả; trang này không đưa benchmark cố định.
8. Checklist phỏng vấn
- Vẽ được hierarchy và giải thích vì sao Map không extends Collection.
- Chọn implementation từ semantics trước complexity.
- Giải thích internals cơ bản của ArrayList, HashMap, TreeMap và PriorityQueue.
- Phân biệt ordered, sorted và insertion/access order.
- Phân biệt unmodifiable view với immutable/defensive copy.
- Biết iterator fail-fast, snapshot và weakly consistent khác nhau.
- Chọn bounded queue khi cần backpressure.
- Không mặc định LinkedList nhanh hơn cho insert/remove.
8.1. Bổ sung · biến checklist thành bằng chứng
| Mục nguồn | Đạt khi giải thích / chứng minh được |
|---|---|
| 1 · Hierarchy | Vẽ riêng nhánh Map; giải thích collection views và superinterfaces sequenced của Java 21; chạy SequencedCheck. |
| 2 · Semantics trước complexity | Viết được phiếu quyết định từ yêu cầu cụ thể, không chọn chỉ vì “O(1)”. |
| 3 · Internals cơ bản | Giải thích backing array/resize, hash buckets/equality, red-black ordering, heap head; chuyển sang Internals cho call flow và 4 failure/performance labs mà nguồn dẫn tới. |
| 4 · Các loại order | Phân biệt sequence/index, insertion order, access order và comparator order; không dựa PriorityQueue iterator để lấy sorted output. |
| 5 · View/copy/immutable | Chạy ViewCopyCheck; chỉ ra được thay đổi nào truyền qua alias, thay đổi nào không. |
| 6 · Ba loại iterator | Chạy IteratorModesCheck; nói rõ vì sao không assert một snapshot toàn map hoặc một CME bắt buộc. |
| 7 · Bounded queue | Chạy QueuePolicyCheck; giải thích timeout/reject policy và vì sao enqueue chưa chứng minh xử lý nghiệp vụ. |
| 8 · LinkedList không mặc định nhanh hơn | Tách traversal cost khỏi insert/remove tại vị trí đã có; đưa cách đo cùng workload thay cho kết luận tuyệt đối. |
8.2. Bổ sung · bốn câu hỏi đào sâu
Trả về unmodifiable view có ngăn dữ liệu đổi sau khi return không?
Không ngăn backing list bị sửa qua reference khác. Chọn shallow copy khi cần ổn định membership/order, và xử lý riêng mutable element graph. [R36] [R38]
ConcurrentHashMap có đủ cho “giảm A, tăng B” như một transaction không?
Không. Atomic operation theo một key không bao phủ hai key và cũng không bảo vệ database side effect. Cần boundary đồng bộ/transaction riêng và test invariant ở cấp nghiệp vụ. [R22]
Tại sao Set “làm mất” một trong hai BigDecimal cùng giá trị?
Kiểm tra implementation và định nghĩa equality. TreeSet dùng comparator equivalence; 1.0 và 1.00 compareTo bằng 0 nhưng equals khác nhau. Phải chọn identity phù hợp domain. [R13] [R15]
Tại sao dùng BlockingQueue vẫn có thể tăng bộ nhớ không giới hạn?
Chặn khi lấy từ queue rỗng không đồng nghĩa giới hạn chiều dài. PriorityBlockingQueue là logically unbounded; LinkedBlockingQueue không chỉ định capacity có bound cực lớn. Cần admission limit và overload policy. [R32] [R31]