for-each — Duyệt collection và mảng không cần index
Tìm hiểu enhanced for từ Java 5, cơ chế Iterable và Iterator, cách phòng tránh ConcurrentModificationException và lựa chọn vòng lặp phù hợp.
TL;DR: Vòng lặp for-each (enhanced for) cung cấp cú pháp súc tích để đọc trực tiếp từng phần tử từ mảng hoặc cấu trúc Iterable mà không cần quản lý biến chỉ số. Trình biên dịch sẽ tự động desugar cú pháp này thành vòng lặp chỉ số (với mảng) hoặc vòng lặp Iterator (với collection), đồng thời kích hoạt cơ chế fail-fast nếu cấu trúc dữ liệu bị can thiệp trái phép.
Khác với vòng lặp for truyền thống nơi bạn phải tự quản lý biến đếm chỉ số i, phần lớn nhu cầu thực tế chỉ đơn thuần là đọc qua từng phần tử. Java 5 giới thiệu cú pháp enhanced for (thường gọi là for-each) để tối giản hóa mẫu thao tác này.
1. Analogy giỏ trái cây minh họa tư duy duyệt tập hợp ra sao?
Bạn có một giỏ trái cây. Bạn không cần biết "trái thứ 3 là gì" — bạn chỉ cần lần lượt lấy từng trái ra xem. Đó là for-each: xin collection "đưa tôi từng element", không bận tâm index.
| Đời thường | for-each |
|---|---|
| Giỏ trái cây | Iterable (List, Set, array...) |
| Lấy từng trái một | Mỗi vòng lặp nhận 1 element |
| Không cần biết "thứ tự" | Không có biến index |
for-each đọc là "for each element in collection" — "với mỗi phần tử trong tập". Tên cũng gợi ý rõ bạn quan tâm phần tử, không quan tâm chỉ số.
2. Cú pháp vòng lặp for-each trong Java được viết như thế nào?
for (<type> <var> : <iterable>) {
// body dung <var>
}
Ví dụ:
int[] arr = {10, 20, 30};
for (int x : arr) {
System.out.println(x);
}
Đọc: "với mỗi int x trong arr, in x".
Hoạt động trên:
- Array — mọi kiểu, kể cả primitive
int[],double[]. - Collection —
List,Set,Queue,Map.entrySet()/keySet()/values(), bất cứ class nào implementIterable<T>.
List<String> names = List.of("An", "Binh", "Chau");
for (String name : names) {
System.out.println("Chao " + name);
}
Set<Integer> ids = Set.of(1, 2, 3);
for (int id : ids) { // auto-unbox Integer -> int
System.out.println(id);
}
Map<String, Integer> score = Map.of("An", 10, "Binh", 8);
for (Map.Entry<String, Integer> e : score.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}
3. Cơ chế Iterable và Iterator bên dưới tầng bytecode hoạt động ra sao?
for-each không phải magic. Compiler rewrite nó thành code dùng Iterator:
// Ban viet:
for (String s : list) { doSomething(s); }
// Compiler sinh ra (don gian hoa):
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
doSomething(s);
}
Cần 2 interface:
Iterable<T>— "tôi có thể cung cấp iterator". Method duy nhất:Iterator<T> iterator();Iterator<T>— "tôi đi qua từng element". Methods:boolean hasNext(),T next(), (optional)void remove().
Bất kỳ class nào implement Iterable<T> đều dùng được với for-each. Bạn có thể tự viết class iterable:
class Range implements Iterable<Integer> {
private final int from, to;
public Range(int from, int to) { this.from = from; this.to = to; }
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
int i = from;
public boolean hasNext() { return i < to; }
public Integer next() { return i++; }
};
}
}
// Dung:
for (int x : new Range(0, 5)) {
System.out.println(x); // 0 1 2 3 4
}
3.1 Trường hợp đặc biệt — Array
Array không implement Iterable (không có iterator()). Compiler xử lý array khác Collection:
// Array: compiler sinh for co index
for (int x : arr) { ... }
// thanh:
for (int i = 0; i < arr.length; i++) {
int x = arr[i];
...
}
// Collection: compiler sinh for dung iterator
for (String s : list) { ... }
// thanh:
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
...
}
Cả 2 cùng cú pháp, nhưng bytecode khác. Với array không có overhead Iterator — tối ưu như for có index.

Thử ngẫmbảng dưới liệt kê "perf-critical trên mảng primitive → dùng for index". Với desugar vừa thấy, for-each trên mảng có thật sự chậm hơn, hay chỉ là thói quen truyền miệng?
4. Khi nào nên dùng for-each và khi nào cần quay về for có index?
| Tình huống | Chọn |
|---|---|
| Chỉ cần đọc element, không quan tâm thứ tự | for-each |
| Cần biết index để truy xuất element khác hoặc in số thứ tự | for index |
| Cần thay đổi element tại index (set giá trị mới) | for index hoặc ListIterator |
| Cần xóa element trong khi duyệt | Iterator.remove() (hoặc removeIf) — KHÔNG for-each rồi list.remove(x) |
| Duyệt ngược | for index với i-- |
| Tính toán perf-critical trên primitive array | for index (ít overhead nhất) |
| Duyệt 2 collection song song theo index | for index (for-each không biết "cùng index") |
4.1 Muốn index kèm element?
Không có cú pháp native. Workaround:
// Cach 1: tu dem
int i = 0;
for (String name : names) {
System.out.println(i + ": " + name);
i++;
}
// Cach 2: for co index
for (int j = 0; j < names.size(); j++) {
System.out.println(j + ": " + names.get(j));
}
// Cach 3 (modern): IntStream + forEach
IntStream.range(0, names.size())
.forEach(k -> System.out.println(k + ": " + names.get(k)));
Python có enumerate(), JavaScript có .entries(). Java 21 vẫn chưa có; chọn cách nào tuỳ style team.
Thử ngẫmbạn quen enumerate() của Python, chuyển sang Java thấy chưa có cú pháp tương đương. Trong ba cách workaround ở trên, cách nào ít có khả năng bạn gõ nhầm chỉ số nhất?
5. Bẫy ngoại lệ ConcurrentModificationException phát sinh khi nào?
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
for (String s : list) {
if (s.equals("b")) {
list.remove(s); // NEM ConcurrentModificationException
}
}
Vì sao? Iterator giữ state (vị trí hiện tại, "mod count" của list). Khi bạn gọi list.remove() trực tiếp (không qua iterator), mod count tăng → iterator lần next() kế tiếp thấy mod count lệch → ném ConcurrentModificationException.
Fix:
// Cach 1: Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("b")) it.remove();
}
// Cach 2: removeIf (Java 8+)
list.removeIf(s -> s.equals("b"));
// Cach 3: build list moi
List<String> keep = new ArrayList<>();
for (String s : list) {
if (!s.equals("b")) keep.add(s);
}
removeIf là ngắn gọn nhất — nội bộ dùng iterator an toàn.
Cả list.add() trong khi for-each cũng ném ConcurrentModificationException. Rule: không modify collection đang duyệt, trừ khi qua iterator của collection đó.
5.1 "Fail-fast" không phải là đảm bảo thread-safety
Mod count check là best-effort — nó không đảm bảo phát hiện mọi race condition giữa thread. Tên gọi "fail-fast" nghĩa: nếu phát hiện được, fail sớm với exception thay vì cho ra dữ liệu sai. Nhưng nó có thể miss case — đừng dựa vào nó để implement concurrent code. Dùng CopyOnWriteArrayList, ConcurrentHashMap cho đa thread.
Thử ngẫm"fail-fast" chỉ best-effort, không đảm bảo an toàn đa luồng. Code cũ từng dựa vào "chưa thấy CME nghĩa là an toàn" để bỏ qua đồng bộ hoá — giả định đó sai ở đâu?
6. Các mẫu ứng dụng for-each phổ biến trong thực tế là gì?
6.1 Sum / product trên mảng
int[] arr = {1, 2, 3, 4, 5};
int sum = 0;
for (int x : arr) sum += x;
Ngắn hơn hẳn so với index-based. JIT optimize tương đương.
6.2 Duyệt Map
Map<String, Integer> counts = Map.of("a", 1, "b", 2, "c", 3);
// Cach 1 — ca key va value
for (Map.Entry<String, Integer> e : counts.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
// Cach 2 — chi key
for (String k : counts.keySet()) {
System.out.println(k);
}
// Cach 3 — chi value
for (int v : counts.values()) {
System.out.println(v);
}
Chọn cái nào tuỳ cần gì. entrySet() hiệu quả nhất khi cần cả key + value — keySet() rồi gọi get(k) là 2 lần lookup.
6.3 Lồng nested
int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
for (int[] row : matrix) {
for (int cell : row) {
System.out.print(cell + " ");
}
System.out.println();
}
7. Các bẫy lỗi thường gặp nhất khi sử dụng for-each là gì?
❌ Nhầm 1: Cố update element qua biến for-each.
for (int x : arr) {
x = x * 2; // KHONG doi arr — x la ban sao
}
✅ Đây là pass-by-value (bài Module 2). x là biến local copy. Muốn sửa mảng, dùng for có index: arr[i] = arr[i] * 2.
❌ Nhầm 2: Modify collection trong for-each.
for (String s : list) {
if (bad(s)) list.remove(s); // ConcurrentModificationException
}
✅ list.removeIf(...), Iterator.remove(), hoặc build list mới.
❌ Nhầm 3: For-each trên null.
List<String> list = null;
for (String s : list) { ... } // NullPointerException
✅ Kiểm tra null trước, hoặc dùng Collections.emptyList() làm default.
❌ Nhầm 4: Unbox Integer null.
List<Integer> list = Arrays.asList(1, null, 3);
for (int x : list) { ... } // NPE khi gap null
✅ Dùng Integer x (wrapper, cho phép null), kiểm tra null trong body.
8. Tài liệu Deep Dive Oracle quy định gì về enhanced for?
Spec / reference chính thức:
- JLS §14.14.2 — The enhanced for statement — spec chính thức, rewrite rules cho array vs Iterable.
- Java API: Iterable — interface chỉ có 1 method bắt buộc, cho phép "default" method
forEach(Java 8). - Java API: Iterator —
hasNext/next/remove,forEachRemaining. - Collection.removeIf — default method Java 8+, thay thế pattern "iterator.remove() trong while".
Ghi chú: JLS §14.14.2 định nghĩa chính xác cú pháp — trong đó có quy tắc: nếu RHS là array, compiler sinh for-index; nếu RHS implement Iterable, compiler sinh iterator loop. Đây là lý do array có performance tương đương for-index, còn Collection có thêm overhead Iterator (nhưng vẫn rất rẻ cho ArrayList).
9. Điểm cốt lõi cần nhớ về vòng lặp for-each là gì?
- Cú pháp
for-eachduyệt mảng và cấu trúcIterablemà không cần quản lý biến đếm index thủ công. - Trình biên dịch tự động desugar cú pháp thành lặp chỉ số đối với mảng và lặp
Iteratorđối với collection. - Tuyệt đối không thêm hoặc xóa phần tử trực tiếp từ collection trong quá trình duyệt để tránh
ConcurrentModificationException. - Biến lặp chỉ là bản sao giá trị (pass-by-value); thay đổi biến lặp sẽ không làm thay đổi dữ liệu trong mảng gốc.
10. Tự kiểm tra kiến thức về vòng lặp for-each như thế nào?
- Q1Đoạn sau in gì? Mảng
arrcó bị thay đổi?int[] arr = {1, 2, 3}; for (int x : arr) { x = x * 10; } for (int x : arr) { System.out.println(x); } - Q2Viết 2 cách xóa mọi phần tử rỗng
""khỏiList<String> list, không gâyConcurrentModificationException. - Q3Vì sao
for-eachkhông có cách lấy index mặc định, trong khi nhiều ngôn ngữ khác (Pythonenumerate, JSentries) đều có? - Q4Khi nào chọn
list.entrySet()thay vìlist.keySet()để duyệtMap? - Q5Viết class
Rangecó thể dùng vớifor-eachđể duyệt các số từstartđếnend-1.
Bài tiếp theo: break và continue — điều khiển luồng bên trong vòng lặp
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Vòng lặp for — Kiểm soát số lần lặp và duyệt mảng
Bài tiếpbreak và continue — Điều khiển luồng trong vòng lặp
Lộ trình Junior Java Backend Developer
Chặng 2/21 · Lập trình từ số 0 với Java — bài 24/32 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này