if rẽ nhánh chạy đúng 1 lần. Nhưng thực tế bạn thường cần lặp lại — đọc từng dòng file đến hết, retry request đến khi thành công, chờ user gõ quit. Những bài toán này không biết trước sẽ lặp bao nhiêu vòng → đúng chỗ của while và do-while.
Bài này giải thích cơ chế 2 loại loop, cách chọn đúng, infinite loop có chủ đích, và vì sao do-while hiếm khi là lựa chọn đầu tiên.
1. Analogy — đọc trang sách
Bạn đọc sách cho đến khi hết:
while— Trước khi mở trang, bạn kiểm tra "còn trang không?". Nếu sách 0 trang, bạn không đọc trang nào.do-while— Bạn mở trang đầu tiên rồi mới hỏi "còn nữa không?". Luôn đọc ít nhất 1 trang, dù sách trống.
| Đời thường | Loop |
|---|---|
| Kiểm tra trước khi làm | while (condition) { body; } |
| Làm trước, kiểm tra sau | do { body; } while (condition); |
💡 💡 Cách nhớ
while= kiểm tra đầu. Body có thể chạy 0 lần.do-while= kiểm tra cuối. Body luôn chạy ít nhất 1 lần.
2. while — kiểm tra điều kiện trước
Cú pháp:
while (<boolean condition>) {
// body
}
Ví dụ đếm xuống:
int n = 3;
while (n > 0) {
System.out.println("Count: " + n);
n--;
}
System.out.println("Done");
Output:
Count: 3
Count: 2
Count: 1
Done
Nếu ban đầu n = 0, body không chạy lần nào — in ngay Done. Đây là điểm khác biệt chính so với do-while.
2.1 Trường hợp dùng while tự nhiên
// Doc file den het
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = reader.readLine()) != null) {
processLine(line);
}
readLine() trả null khi hết file. Không biết trước file có bao nhiêu dòng → dùng while.
// Retry den khi thanh cong hoac het luot
int attempt = 0;
boolean ok = false;
while (!ok && attempt < 3) {
ok = callApi();
attempt++;
}
// Xu ly user input den khi quit
Scanner sc = new Scanner(System.in);
String cmd = "";
while (!cmd.equals("quit")) {
System.out.print("> ");
cmd = sc.nextLine();
runCommand(cmd);
}
3. do-while — kiểm tra sau
do {
// body
} while (<boolean condition>); // chu y dau ;
Body chạy ít nhất 1 lần, rồi kiểm tra điều kiện, lặp nếu đúng.
int n;
Scanner sc = new Scanner(System.in);
do {
System.out.print("Nhap so tu 1 den 10: ");
n = sc.nextInt();
} while (n < 1 || n > 10);
System.out.println("OK: " + n);
Đây là use case điển hình: prompt ít nhất 1 lần, re-prompt nếu input sai. Logic tự nhiên hơn viết bằng while.
3.1 Vì sao do-while hiếm?
Trong thực tế, 90%+ loop cần kiểm tra trước để tránh chạy oan:
- Đọc file → kiểm tra có dòng không trước khi xử lý.
- Duyệt collection → kiểm tra còn element không trước khi
next(). - API retry → kiểm tra chưa hết lượt trước khi gọi.
do-while chỉ tự nhiên khi bước đầu tiên phải chạy (prompt, initialize). Nhiều team thậm chí có coding convention "tránh do-while" vì gây nhầm — reader nhìn do { không thấy điều kiện ngay trên, phải cuộn xuống cuối block mới biết loop chạy khi nào.
4. Cơ chế bên dưới — bytecode của loop
Compiler biên dịch loop thành label + conditional jump + unconditional goto. Ví dụ:
int i = 0;
while (i < 3) {
System.out.println(i);
i++;
}
Bytecode (javap -c):
iconst_0
istore_1 // i = 0
L_START:
iload_1
iconst_3
if_icmpge L_END // neu i >= 3 nhay toi L_END
// body
iload_1
invokevirtual println
iinc 1, 1 // i++
goto L_START // lap lai
L_END:
return
while = kiểm tra trước:
L_START→ test → nếu fail, nhảyL_END(thoát).- Chạy body →
goto L_START(quay lại test).
do-while thì đơn giản hơn 1 instruction — bỏ test ở đầu:
L_START:
// body
<test>
if_icmplt L_START // neu dung, quay lai
// fall through thoat
ℹ️ 📚 Vì sao đáng biết?
JIT compiler tối ưu loop rất mạnh — loop unrolling, invariant hoisting, vectorization. Hiểu loop = label + goto giúp bạn đọc được profiler output, biết tại sao thay while thành for có khi đổi hiệu năng (hint cho JIT khác), và tại sao "empty loop" bị optimize bỏ hoàn toàn trong release build.
5. Infinite loop có chủ đích
Đôi khi bạn muốn loop chạy mãi, thoát bằng break ở giữa:
while (true) {
String msg = queue.poll();
if (msg == null) break;
handle(msg);
}
Ba cách viết infinite loop trong Java:
while (true) { ... } // thong dung nhat, ro nghia
for (;;) { ... } // cu phap tu C, hiem dung trong Java
do { ... } while (true); // hau nhu khong ai viet
Cả 3 compile thành bytecode tương đương. Chọn while (true) vì reader nhìn 1 giây hiểu ngay.
⚠️ ⚠️ Infinite loop ngoài ý muốn — dead lock / freeze
Loop không có điều kiện thoát rõ ràng → treo app:
int i = 0;
while (i < 10) {
if (i == 5) continue; // quen i++, i mac ket o 5 mai mai
doWork(i);
i++;
}
Tip debug: nếu app đột ngột tốn 100% CPU mà không có log mới, check các loop gần đây. jstack <pid> cho thấy thread đang kẹt ở đâu.
6. So sánh while / do-while / for
| Tình huống | Nên dùng | Vì sao |
|---|---|---|
| Biết trước số lần lặp (0..n-1) | for (bài sau) | Init/condition/update gọn 1 dòng |
| Lặp đến khi có điều kiện dừng, chưa biết bao nhiêu lần | while | Kiểm tra trước, an toàn với tập rỗng |
| Phải chạy ít nhất 1 lần (prompt user) | do-while | Body trước, test sau — logic tự nhiên |
| Loop vô hạn thoát ở giữa | while (true) { ... break; } | Rõ ý đồ |
7. Pitfall tổng hợp
❌ Nhầm 1: Quên update biến điều khiển → infinite loop.
int i = 0;
while (i < 10) {
System.out.println(i);
// quen i++
}
✅ Đảm bảo biến trong điều kiện có đường được thay đổi trong mỗi vòng.
❌ Nhầm 2: do-while thiếu dấu ; ở cuối.
do { ... } while (cond) // COMPILE ERROR — thieu ;
✅ Cú pháp do { ... } while (cond); — dấu ; bắt buộc, khác hẳn while bình thường.
❌ Nhầm 3: So sánh double trong điều kiện dừng.
double x = 0;
while (x != 1.0) { x += 0.1; } // KHONG bao gio dung — floating point
✅ Với float/double, dùng while (x < 1.0) hoặc check Math.abs(x - 1.0) < EPS.
❌ Nhầm 4: Lưu reference vào collection và modify trong khi lặp.
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
int i = 0;
while (i < list.size()) {
list.remove(0); // size giam trong khi lap — skip element
i++;
}
✅ Dùng Iterator.remove() hoặc list.removeIf(...); xem chi tiết ở bài for-each.
8. 📚 Deep Dive Oracle
ℹ️ 📚 Deep Dive Oracle (optional)
Spec / reference chính thức:
- JLS §14.12 — The while Statement — cú pháp và ngữ nghĩa chính xác.
- JLS §14.13 — The do Statement — giải thích vì sao body luôn chạy ít nhất 1 lần.
- JLS §14.15 — The break Statement — break thoát loop.
Ghi chú: Spec ngắn gọn cho while/do-while — 2 loại loop này chỉ khác nhau vị trí test condition. Quan trọng nhất là hiểu semantics "pre-test vs post-test" và tránh infinite loop ngoài ý muốn. Bài tiếp theo về for có spec dày hơn vì có init/condition/update.
9. Tóm tắt
while (cond) { body }— kiểm tra trước; body có thể chạy 0 lần.do { body } while (cond);— kiểm tra sau; body luôn chạy ít nhất 1 lần. Chú ý dấu;cuối.- Bytecode của loop = label + conditional jump + goto. Cơ chế giống nhau cho mọi loop construct.
do-whilehiếm hơn — chỉ tự nhiên khi bước đầu bắt buộc (prompt, initialize).- Biến điều khiển phải được update trong body, không thì infinite loop.
- Infinite loop có chủ đích:
while (true) { ... break; }. - So sánh
double/floattrong điều kiện dừng dễ sai — dùng<,>hoặcabs(a-b) < eps.
10. Tự kiểm tra
Q1Đoạn sau in gì?int n = 0;
while (n > 0) {
System.out.println(n);
n--;
}
int m = 0;
do {
System.out.println(m);
m--;
} while (m > 0);
▸
int n = 0;
while (n > 0) {
System.out.println(n);
n--;
}
int m = 0;
do {
System.out.println(m);
m--;
} while (m > 0);Đoạn while không in gì — n = 0 không thoả n > 0 ngay từ đầu, body bỏ qua.
Đoạn do-while in 0 — body chạy trước, in m = 0, rồi m-- → m = -1, kiểm tra -1 > 0 sai, thoát.
Đây là điểm khác biệt duy nhất giữa 2 loại loop: do-while luôn chạy body ít nhất 1 lần.
Q2Khi nào nên chọn do-while thay vì while?▸
do-while thay vì while?Khi bước đầu tiên bắt buộc phải chạy trước khi có thể đánh giá điều kiện thoát. Ví dụ điển hình:
- Prompt user: phải hỏi ít nhất 1 lần để có input; kiểm tra input hợp lệ sau đó.
do { input = sc.nextInt(); } while (input < 0); - Menu lặp: hiện menu → nhận lựa chọn → nếu lựa chọn "thoát" thì dừng, ngược lại hiện lại.
- Retry với initial attempt: thử lần 1, sau đó kiểm tra có cần retry không.
Nếu có thể check điều kiện trước khi làm bất cứ gì (vd đọc collection, có empty), dùng while — an toàn hơn với edge case input rỗng.
Q3Vì sao while (x != 1.0) x += 0.1; khởi tạo x = 0 có thể chạy vô hạn?▸
while (x != 1.0) x += 0.1; khởi tạo x = 0 có thể chạy vô hạn?0.1 không biểu diễn chính xác được bằng binary floating-point (IEEE 754). Mỗi lần cộng 0.1 tích lũy sai số. Sau 10 lần cộng, x có thể là 0.9999999999999999 hoặc 1.0000000000000002 chứ không đúng 1.0. x != 1.0 mãi mãi true → infinite loop. Fix: dùng x < 1.0 (tolerance 1 chiều) hoặc Math.abs(x - 1.0) < 1e-9 (epsilon comparison). Hoặc đổi sang lặp bằng int: for (int i = 0; i < 10; i++) x = 0.1 * i;Q4Đoạn sau có lỗi gì?int i = 0;
while (i < 10) {
if (i == 5) continue;
System.out.println(i);
i++;
}
▸
int i = 0;
while (i < 10) {
if (i == 5) continue;
System.out.println(i);
i++;
}i = 5. Khi i == 5, continue nhảy về đầu loop trước khi gặp i++ → i vẫn là 5 → điều kiện i < 10 vẫn đúng → lặp lại, lại gặp continue, kẹt mãi. Fix: đưa i++ lên trước logic skip, hoặc dùng for vì for chạy update cả khi có continue. Xem bài tiếp theo về for.Q5Viết 3 cách tạo infinite loop trong Java và chọn cách tốt nhất.▸
Ba cách tương đương bytecode:
while (true) { ... }for (;;) { ... }do { ... } while (true);
Tốt nhất: while (true). Đọc 1 giây hiểu ngay ý định. for (;;) di sản từ C, ít dùng trong Java. do-while (true) khó đọc — phải cuộn xuống cuối mới biết condition, thêm 1 dòng noise. Luôn có đường thoát bằng break ở giữa hoặc return — không thì app treo.
Bài tiếp theo: Vòng lặp for — đếm có chủ đích