Backend

99 Ngày Java — Ngày 06: Câu lệnh điều kiện if & switch

SSite Admin
3 tháng 08, 2026 6 phút đọc 32 lượt xem
99 Ngày Java — Ngày 06: Câu lệnh điều kiện if & switch

Năm ngày qua, chương trình của ta chạy thẳng một mạch từ dòng đầu tới dòng cuối. Hôm nay code học kỹ năng quan trọng nhất của mọi ngôn ngữ: rẽ nhánh. Ta đi từ if–else if quen thuộc, qua guard clause giúp code phẳng dễ đọc, đến switch — cả bản cổ điển đầy bẫy lẫn switch expression hiện đại mà Java 21 khuyến khích dùng.

Sketchnote Ngày 06: if–else, guard clause và switch expression trong Java

if – else if: chạy nhánh đúng đầu tiên rồi dừng

Điều kiện trong ngoặc bắt buộc là boolean — chính là các biểu thức so sánh và logic của Ngày 04; khác với C, số nguyên không tự biến thành đúng/sai. Java xét các nhánh từ trên xuống, gặp điều kiện đúng đầu tiên là chạy nhánh đó rồi bỏ qua toàn bộ phần còn lại:

int diem = 82;

if (diem >= 90) {
    System.out.println("Xuất sắc");
} else if (diem >= 75) {
    System.out.println("Giỏi");     // ← 82 rơi vào đây rồi DỪNG
} else if (diem >= 60) {
    System.out.println("Khá");      // 82 >= 60 cũng đúng — nhưng không bao giờ xét tới
} else {
    System.out.println("Cần cố gắng");
}

Với diem = 82, cả >= 75 lẫn >= 60 đều đúng — nhưng chỉ "Giỏi" được in vì nhánh đứng trước thắng. Bài học: xếp điều kiện từ chặt đến lỏng; đảo thứ tự là 82 thành "Khá" ngay.

Guard clause — chặn sớm, thoát sớm

Cứ mỗi tầng if lồng nhau, người đọc phải giữ thêm một điều kiện trong đầu — lồng 3–4 tầng thành "mũi tên chết chóc" trứ danh. Guard clause đảo ngược tư duy: kiểm tra trường hợp xấu trước và thoát ngay, để luồng chính nằm phẳng ở cuối:

// if lồng 3 tầng — "mũi tên chết chóc", đọc mệt
if (user != null) {
    if (user.isActive()) {
        if (user.hasPermission()) {
            doWork();
        }
    }
}

// Guard clause — chặn sớm, thoát sớm, luồng chính nằm phẳng
if (user == null) return;
if (!user.isActive()) return;
if (!user.hasPermission()) return;
doWork();

return sống trong method — chủ đề của Ngày 10 — nhưng nguyên tắc thì dùng được từ hôm nay: việc đặc biệt xử trước, việc chính để phẳng. Đây là một trong những thói quen tách code dễ đọc khỏi code "leo thang".

switch cổ điển — và cái bẫy fall-through

Khi so sánh một giá trị với nhiều hằng số, chuỗi else if dài lê thê — switch sinh ra cho việc này. Nhưng bản cổ điển mang cái bẫy nổi tiếng nhất lịch sử Java: quên break là thực thi "rơi xuyên" (fall-through) xuống case kế tiếp:

int thu = 3;
switch (thu) {
    case 2:
        System.out.println("Đầu tuần");
        break;              // quên break là "rơi xuyên" xuống case dưới!
    case 3:
    case 4:
    case 5:
        System.out.println("Giữa tuần");
        break;
    case 6:
        System.out.println("Sắp cuối tuần");
        break;
    default:
        System.out.println("Cuối tuần");
}
  • Switch cổ điển nhận int, char, String, enum (Ngày 23)…

  • Fall-through đôi khi hữu ích — gộp case 3: case 4: case 5: như trên — nhưng đa số lần xuất hiện là bug.

  • default không bắt buộc, nhưng nên có để đỡ "im lặng khó hiểu" khi giá trị lạ lọt vào.

switch expression — switch đời mới trả về giá trị

Từ Java 14, switch còn là biểu thức: dùng mũi tên ->, không fall-through, và trả thẳng giá trị để gán vào biến:

int thu = 3;
String loai = switch (thu) {
    case 2 -> "Đầu tuần";
    case 3, 4, 5 -> "Giữa tuần";   // nhiều nhãn trên một dòng
    case 6 -> "Sắp cuối tuần";
    case 7, 1 -> "Cuối tuần";
    default -> {
        System.out.println("Thứ lạ: " + thu);
        yield "Không rõ";          // trong block, trả giá trị bằng yield
    }
};
System.out.println(loai);          // Giữa tuần
  • Mũi tên -> không rơi xuyên — mỗi case độc lập, không cần break.

  • Nhiều nhãn chung một nhánh: case 3, 4, 5 -> — gọn hơn hẳn bản cổ điển.

  • Nhánh cần nhiều câu lệnh thì mở block { } và trả giá trị bằng yield.

  • Switch expression bắt buộc đủ mọi trường hợp (exhaustive) — thiếu là lỗi compile; kết hợp enum sẽ thấy sức mạnh thật sự ở Ngày 23, còn pattern matching hẹn Ngày 82.

Lời khuyên đơn giản: mặc định dùng switch expression — an toàn hơn, gọn hơn; chỉ gặp bản cổ điển khi đọc code cũ.

Màn hình code — nơi mỗi dòng if quyết định một ngã rẽ của chương trình

Ba cái bẫy kinh điển

// Bẫy 1: if không ngoặc — chỉ câu lệnh ĐẦU TIÊN thuộc if
if (diem >= 60)
    System.out.println("Đậu");
    System.out.println("Chúc mừng!");  // LUÔN chạy — thụt lề đánh lừa mắt!

// Bẫy 2: không có so sánh khoảng kiểu toán học
// if (0 <= diem <= 100)              // ❌ không compile — phải tách:
if (diem >= 0 && diem <= 100) { }     // ✓

// Bẫy 3: gán nhầm trong điều kiện boolean
boolean dangMo = false;
if (dangMo = true) { }                // gán, không phải so sánh — luôn true!
// May mắn: với số, if (x = 5) không compile vì điều kiện phải là boolean

Bẫy 1 nguy hiểm tới mức nhiều team quy định luôn mở ngoặc nhọn kể cả một câu lệnh — thụt lề không có ý nghĩa với compiler. Bẫy 2 là phản xạ từ toán học mà Java không cho phép: khoảng giá trị phải tách bằng &&. Bẫy 3 hiếm nhưng cay: với biến boolean, dấu = lọt qua compiler và điều kiện luôn đúng — thêm một lý do để so sánh kiểu if (dangMo) thay vì if (dangMo == true).

Bài tập nhỏ

  • Viết if–else if xếp loại điểm 0–10: dưới 5 yếu, dưới 6.5 trung bình, dưới 8 khá, còn lại giỏi — rồi thử đảo thứ tự các nhánh xem kết quả sai thế nào.

  • Dùng switch expression đổi số tháng 1–12 thành số ngày trong tháng (tháng 2 cứ trả 28) — các tháng 31 ngày gộp chung một nhãn.

  • Xóa hết break trong ví dụ switch cổ điển với thu = 3 — đoán trước xem in ra những dòng nào, rồi chạy kiểm chứng.

  • Viết lại một đoạn if lồng 3 tầng bất kỳ của bạn bằng guard clause.

Kết luận

Code của bạn giờ biết rẽ nhánh: if–else if chạy nhánh đúng đầu tiên, guard clause giữ luồng chính phẳng, switch cổ điển cần break còn switch expression an toàn và trả giá trị. Ngày 07, mảnh ghép còn thiếu của điều khiển luồng: vòng lặp for, while, do-while — code không chỉ biết rẽ mà còn biết lặp. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9