Java — Từ Zero đến Senior/Điều kiện & Vòng lặp/`break` và `continue` — điều khiển luồng bên trong vòng lặp
6/7
~14 phútĐiều kiện & Vòng lặp

`break` và `continue` — điều khiển luồng bên trong vòng lặp

break thoát loop sớm, continue skip sang vòng kế, labeled break/continue cho nested loop. Khi nào dùng, khi nào tránh và vì sao nhiều team cấm labeled.

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 breakcontinue.

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ườngTừ khóa
Gấp sách luônbreak — thoát loop
Lật qua trang kếcontinue — sang vòng kế
Gấp 2 cuốn sách cùng lúcbreak <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-each thay vì for có 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 whilefor 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 breakcontinue 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ãn outer.
  • continue outer; — bỏ qua phần còn lại và nhảy lên test của loop outer.

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/continueO(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?

gotoconst 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ốngCú pháp
Tìm thấy → dừngbreak 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 methodreturn
Ném lỗithrow 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 forfor 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:

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à goto có cấu trúc; O(1), không penalty hiệu năng.
  • Trong while/do-while, continue không chạy phần update — phải tự update trước continue để tránh infinite loop.
  • for chạy update sau continue — an toàn hơn.
  • Dùng label ít — thường extract method + return đọc rõ hơn.
  • return trong loop thoát cả method — hay cho hàm tìm kiếm.

11. Tự kiểm tra

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);
}

In 0, 1, 3.

  • i=0: cả 2 điều kiện sai, in 0.
  • i=1: in 1.
  • i=2: continue → bỏ in, nhảy tới i++.
  • i=3: in 3.
  • i=4: break → thoát loop, không in 4.
Q2
Vì 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++;
}

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 updatewhile 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 forcontinue tự chạy i++.

Q3
Viế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;
        }
    }
}

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 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.
Q5
Khi 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?

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