Java Foundations
25/39
Bài 25 / 39~12 phútĐiều kiện & Vòng lặpLộ trình · chặng 02/21Miễn phí… lượt xem

break và continue — Điều khiển luồng trong vòng lặp

Tìm hiểu break thoát loop, continue bỏ qua vòng lặp, labeled statements cho nested loop và cơ chế unconditional jump ở tầng bytecode Java.

TL;DR: break lập tức kết thúc vòng lặp gần nhất chứa nó, trong khi continue bỏ qua phần còn lại của thân vòng lặp hiện tại để chuyển sang bước kiểm tra hoặc cập nhật kế tiếp. Cả hai đều biên dịch thành lệnh goto vô điều kiện ở bytecode với độ phức tạp $O(1)$.

Khi xây dựng các cấu trúc như vòng lặp for, chương trình mặc định chạy tuần tự cho đến khi điều kiện dừng thỏa mãn. Tuy nhiên trong thực tế, bạn thường cần thoát sớm (ví dụ tìm thấy dữ liệu cần tìm) hoặc bỏ qua một lượt lặp (ví dụ dữ liệu đầu vào không hợp lệ). break và continue chính là hai công cụ điều khiển luồng tức thời cho các tình huống này.

1. Analogy đọc sách tìm từ minh họa break và continue ra sao?

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. Từ khóa break thoát khỏi vòng lặp như thế nào?

Java
// 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. Từ khóa continue bỏ qua vòng hiện tại hoạt động ra sao?

Java
// 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

Java
// 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.

Gốc rễ của cả hai từ khoá nằm ở chỗ chúng nhảy tới đâu, chứ không ở chỗ chúng "dừng" hay "bỏ qua":

Vòng lặp for với ba đường rời thân đi ba đích khác nhau, continue tới ô update, break ra ngoài vòng lặp, điều kiện sai thoát theo lối thường

continue không quay về đầu vòng — nó nhảy tới ô update rồi mới kiểm tra lại. while không có ô update nào, nên continue ở đó nhảy thẳng lên ô kiểm tra và bỏ qua luôn dòng tăng biến nằm trong thân. Cùng một từ khoá, hai đích khác nhau — đó là toàn bộ câu chuyện của bug ở trên.

Thử ngẫmbug i mắc kẹt ở 5 mãi mãi phía trên xảy ra vì continue trong while không tự tăng biến. Đổi while đó thành for, giữ nguyên dòng code cũ, bug này còn xảy ra không?

4. Labeled break và continue xử lý vòng lặp lồng nhau ra sao?

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:

Java
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>:

Java
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

Java
// 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.

Thử ngẫmteam bạn cấm labeled break/continue, ép mọi nested loop qua extract method như trên. Một vòng lặp 4 tầng chia sẻ 5 biến local thì luật đó có còn hợp lý không?

5. Cơ chế nhảy unconditional goto ở tầng bytecode hoạt động như thế nào?

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

Thử ngẫmbreak/continue compile thẳng thành goto, O(1), không unwind stack như exception. Thay một break bằng throw để thoát loop cho "gọn" — chi phí thật sự đổi ở đâu?

6. Lệnh return bên trong vòng lặp có lợi thế gì so với break?

Java
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 nên dùng break, continue hay return?

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. Các bẫy lỗi thường gặp nhất với break và continue là gì?

❌ Nhầm 1: continue trong while làm infinite loop do quên update.

Java
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".

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

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

Java
outer: for (...) {
    outer: for (...) {  // COMPILE ERROR — label duplicate trong cung scope
    }
}

✅ Dùng tên khác nhau, hoặc refactor method.

9. Tài liệu Deep Dive Oracle quy định gì về break và continue?

📚 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. Điểm cốt lõi cần nhớ về break và continue là gì?

  • break lập tức dừng và thoát khỏi cấu trúc lặp hoặc switch gần nhất với độ phức tạp O(1).
  • continue bỏ qua phần còn lại của thân vòng lặp hiện tại; trong for nó vẫn chạy bước cập nhật biến đếm.
  • Labeled statement hữu ích để thoát khỏi vòng lặp đa tầng nhưng nên ưu tiên tách method kết hợp lệnh return sớm.
  • Bytecode sử dụng lệnh goto có cấu trúc để điều hướng luồng thực thi an toàn và tối ưu hóa hiệu năng.

11. Tự kiểm tra kiến thức về break và continue như thế nào?

Tự kiểm tra
0/5 câu đã trả lời
  1. 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);
    }
  2. 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++;
    }
  3. 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;
            }
        }
    }
  4. 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");
    }
  5. 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?

Bài tiếp theo: Mini-challenge: FizzBuzz với switch expression

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.

Lộ trình Junior Java Backend Developer

Chặng 2/21 · Lập trình từ số 0 với Java — bài 25/32 của chặng

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Mini-challenge: FizzBuzz với switch expression