Vòng lặp chuẩn chạy đủ số vòng đã định, nhưng thực tế nhiều khi bạn cần thoát sớm (tìm thấy rồi, không cần duyệt nữa) hoặc bỏ qua vòng hiện tại (input bẩn, nhảy sang vòng kế). Đó là công việc của break và continue.
Bài này giải thích 2 từ khóa này, cơ chế nhảy ở bytecode, labeled break/continue cho nested loop, và vì sao dùng nhiều break giữa loop thường là dấu hiệu cần refactor thành method riêng.
1. Analogy — đọc sách tìm từ
Bạn lật từng trang tìm 1 từ.
break— Tìm thấy từ → gấp sách, không đọc tiếp.continue— Trang này bị rách không đọc được → lật trang kế tiếp, chưa gấp sách.
| Đời thường | Từ khóa |
|---|---|
| Gấp sách luôn | break — thoát loop |
| Lật qua trang kế | continue — sang vòng kế |
| Gấp 2 cuốn sách cùng lúc | break <label> (nested) |
💡 💡 Cách nhớ
break= thoát loop.continue= tiếp tục sang vòng kế (skip phần còn lại của body vòng hiện tại).
2. break — thoát loop
// Tim so chia het cho 7 dau tien trong mang
int[] arr = {3, 8, 14, 21, 45, 63};
int found = -1;
for (int x : arr) {
if (x % 7 == 0) {
found = x;
break; // khong can duyet tiep
}
}
System.out.println(found); // 14
break thoát loop bao ngoài gần nhất. Sau break, JVM nhảy đến statement kế sau loop.
Lợi ích
- Không phí thời gian duyệt element còn lại sau khi đã có kết quả.
- Cho phép dùng
for-eachthay vìforcó index với điều kiện sớm.
3. continue — bỏ qua vòng hiện tại
// Tinh tong cac so chan
int[] arr = {1, 2, 3, 4, 5, 6};
int sum = 0;
for (int x : arr) {
if (x % 2 != 0) continue; // so le -> bo qua, sang vong ke
sum += x; // chi chay cho so chan
}
System.out.println(sum); // 2+4+6 = 12
continue nhảy về đầu loop (với while / do-while) hoặc tới phần update (với for), bỏ phần còn lại của body.
3.1 Khác biệt giữa while và for khi gặp continue
// for — continue chay phan update (i++)
for (int i = 0; i < 10; i++) {
if (i == 5) continue;
System.out.println(i); // in 0 1 2 3 4 6 7 8 9 — in du tru 5
}
// while — continue KHONG chay i++
int i = 0;
while (i < 10) {
if (i == 5) continue; // BUG — i mac ket o 5 mai mai
System.out.println(i);
i++;
}
Với while, bạn phải tự đặt i++ trước continue, hoặc tái cấu trúc.
4. Labeled break và continue cho nested loop
break/continue chỉ thoát/skip loop gần nhất. Nhưng với nested loop, đôi khi bạn cần thoát cả 2–3 tầng:
int[][] matrix = {{1,2,3},{4,5,6},{7,8,9}};
int target = 5;
boolean found = false;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == target) {
found = true;
break; // chi thoat vong j, van o trong vong i
}
}
if (found) break; // them 1 break nua — clunky
}
Java có labeled loop — đặt nhãn trước loop và dùng break <label> / continue <label>:
outer:
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == target) {
System.out.println("found at (" + i + "," + j + ")");
break outer; // thoat ca 2 vong mot luc
}
}
}
Cú pháp:
- Nhãn = identifier + dấu
:, đặt ngay trước loop:outer:,search:. break outer;— thoát loop có nhãnouter.continue outer;— bỏ qua phần còn lại và nhảy lên test của loopouter.
Label không phải goto tự do — chỉ dùng được với break/continue trỏ tới một loop đang bao ngoài.
⚠️ ⚠️ Label là công cụ, không phải gợi ý phong cách
Nhiều team có coding rule cấm labeled break/continue. Không phải vì nó "xấu" về kỹ thuật, mà vì thường có cách viết rõ hơn:
- Extract method — chuyển nested loop thành method riêng, dùng
returnđể thoát mọi tầng cùng lúc. - Stream / findFirst — duyệt nested với stream API gọn hơn.
Label chỉ xứng đáng khi method-extract làm code khó đọc hơn (vd loop ngắn, chia sẻ nhiều biến local).
4.1 Extract method — cách sạch hơn
// Thay vi labeled break
int[] location = findLocation(matrix, target);
if (location != null) {
System.out.println("found at " + Arrays.toString(location));
}
// Helper method
static int[] findLocation(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == target) return new int[]{i, j};
}
}
return null;
}
return thoát method → thoát mọi loop. Ngắn, dễ test, dễ tái dùng.
5. Cơ chế ở bytecode
break, continue, break label, continue label đều compile thành goto (unconditional jump). Java không expose goto cho programmer dùng trực tiếp, nhưng nó vẫn tồn tại ở bytecode — chỉ giới hạn trong cú pháp loop/switch/labeled.
// break
goto L_END
// continue trong for
goto L_UPDATE
// break <label>
goto L_END_OF_LABELED_LOOP
Đây là lý do break/continue là O(1) — không phải "unwind stack" như exception. Dùng chúng không có penalty hiệu năng.
ℹ️ 📚 Tại sao Java có `goto` reserved keyword nhưng cấm dùng?
goto và const là 2 keyword được reserved trong Java nhưng không có ngữ nghĩa. Lý do: designers giữ khả năng thêm vào tương lai, nhưng nhận ra việc cho phép goto tự do sẽ phá hủy tính "structured programming". Thay vào đó chỉ support break/continue/break label — đủ cho mọi use case mà không cho viết "spaghetti code".
6. return trong loop — thoát cả method
public int findIndex(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) return i; // thoat method luon
}
return -1;
}
return không chỉ thoát loop, mà thoát cả method. Khi loop chỉ có 1 mục đích (tìm → trả về) thì return trong loop + return default cuối method thường sạch hơn break + biến found.
7. Khi nào dùng cái gì?
| Tình huống | Cú pháp |
|---|---|
| Tìm thấy → dừng | break hoặc return |
| Skip element không hợp lệ | continue |
| Thoát nested loop (2+ tầng) | break <label> hoặc extract method + return (ưu tiên) |
| Thoát hoàn toàn method | return |
| Ném lỗi | throw new ... |
8. Pitfall tổng hợp
❌ Nhầm 1: continue trong while làm infinite loop do quên update.
int i = 0;
while (i < 10) {
if (i == 5) continue; // BUG — i mac ket o 5
i++;
}
✅ Update biến trước continue, hoặc dùng for vì for tự chạy update phần.
❌ Nhầm 2: break trong switch expression không phải "thoát loop".
for (int x : arr) {
switch (x) {
case 1: break; // thoat switch, khong thoat for
}
System.out.println(x); // van chay
}
✅ Biết rõ scope của break — nó thoát loop/switch gần nhất. Dùng label nếu cần thoát cả hai.
❌ Nhầm 3: Dùng quá nhiều break giữa loop → logic khó đọc.
for (...) {
if (cond1) break;
...
if (cond2) break;
...
if (cond3) break;
...
}
✅ Nghĩ lại: có phải đây là 1 method riêng? return sớm với nhiều condition clear hơn.
❌ Nhầm 4: Labeled break gây rối khi nhiều label cùng tên.
outer: for (...) {
outer: for (...) { // COMPILE ERROR — label duplicate trong cung scope
}
}
✅ Dùng tên khác nhau, hoặc refactor method.
9. 📚 Deep Dive Oracle
ℹ️ 📚 Deep Dive Oracle (optional)
Spec / reference chính thức:
- JLS §14.15 — The break Statement — cú pháp, cả labeled.
- JLS §14.16 — The continue Statement — quy tắc nhảy đến test vs update.
- JLS §14.7 — Labeled Statements — label có thể gắn với bất kỳ statement, nhưng chỉ loop/switch mới có nghĩa với break/continue.
- JLS §3.9 — Keywords — danh sách keyword reserved, bao gồm
gotovàconst.
Ghi chú: JLS §14.7 cho phép label gắn với if, block { }, nhưng chỉ break <label> trỏ tới được. continue chỉ dùng với loop-label. Đây là thiết kế có chủ đích — giữ cho control flow luôn có structure, không "nhảy ngang ngược" như goto C.
10. Tóm tắt
break— thoát loop/switch gần nhất.continue— bỏ phần còn lại của body, sang vòng kế tiếp.break <label>/continue <label>— tác động lên loop có nhãn trong outer scope.- Label = identifier +
:đặt trước loop. - Bytecode là
gotocó cấu trúc; O(1), không penalty hiệu năng. - Trong
while/do-while,continuekhông chạy phần update — phải tự update trướccontinueđể tránh infinite loop. forchạyupdatesaucontinue— an toàn hơn.- Dùng label ít — thường extract method +
returnđọc rõ hơn. returntrong loop thoát cả method — hay cho hàm tìm kiếm.
11. Tự kiểm tra
Q1Đoạn sau in gì?for (int i = 0; i < 5; i++) {
if (i == 2) continue;
if (i == 4) break;
System.out.println(i);
}
▸
for (int i = 0; i < 5; i++) {
if (i == 2) continue;
if (i == 4) break;
System.out.println(i);
}In 0, 1, 3.
i=0: cả 2 điều kiện sai, in0.i=1: in1.i=2:continue→ bỏ in, nhảy tớii++.i=3: in3.i=4:break→ thoát loop, không in4.
Q2Vì sao đoạn sau gây infinite loop? Sửa.int i = 0;
while (i < 10) {
if (i % 2 == 0) continue;
System.out.println(i);
i++;
}
▸
int i = 0;
while (i < 10) {
if (i % 2 == 0) continue;
System.out.println(i);
i++;
}Lần đầu, i = 0, i % 2 == 0 đúng → continue → nhảy về test, i vẫn là 0. Lặp lại mãi — infinite loop.
Khác với for, continue trong while không chạy update vì while không có update section.
Fix 1: đưa i++ lên trước:
int i = 0;
while (i < 10) {
i++; // luon increment
if (i % 2 == 0) continue;
System.out.println(i);
}Fix 2: dùng for — continue tự chạy i++.
Q3Viết lại đoạn sau không dùng labeled break mà vẫn đúng logic:outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i * j > 30) {
System.out.println(i + "," + j);
break outer;
}
}
}
▸
outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i * j > 30) {
System.out.println(i + "," + j);
break outer;
}
}
}Extract thành method riêng, dùng return:
public static void findPair() {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i * j > 30) {
System.out.println(i + "," + j);
return; // thoat ca 2 vong qua return
}
}
}
}Ưu điểm: code sạch, method có tên tự giải thích, có thể test riêng, không cần label. Đây là lý do nhiều team cấm labeled break — method extraction luôn là alternative tốt.
Q4Đoạn sau break thoát cấu trúc nào?for (int x : arr) {
switch (x) {
case 1:
break;
}
System.out.println("after switch");
}
▸
break thoát cấu trúc nào?for (int x : arr) {
switch (x) {
case 1:
break;
}
System.out.println("after switch");
}break thoát switch (statement gần nhất), không thoát for. Sau case 1, break nhảy ra khỏi switch → in "after switch" → vòng for tiếp tục với x tiếp theo. Nếu muốn thoát cả for, phải dùng labeled: outer: for (...) { switch (x) { case 1: break outer; } }. Hoặc dùng switch expression case 1 -> để tránh hiểu lầm hoàn toàn — không cần/không cho break.Q5Khi viết hàm tìm kiếm int findIndex(int[] arr, int target), bạn chọn break + bien found hay return sớm? Vì sao?▸
int findIndex(int[] arr, int target), bạn chọn break + bien found hay return sớm? Vì sao?return sớm. Code ngắn hơn:
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) return i;
}
return -1;So với:
int found = -1;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
found = i;
break;
}
}
return found;Lý do: method có 1 trách nhiệm duy nhất (tìm index). return sớm ngay khi có kết quả rõ ý định, ít biến state, ít dòng. Bản break + found chỉ cần khi loop có logic tiếp sau (xử lý post-loop, cleanup, ghi log).
Bài tiếp theo: Mini-challenge: FizzBuzz với switch expression