if / else / else if — rẽ nhánh theo điều kiện
Hiểu if/else trong Java từ cú pháp đến cơ chế short-circuit, dangling-else, ternary operator, và vì sao nested if sâu là dấu hiệu cần refactor.
Chương trình từ đầu Module 1 đến giờ đều chạy tuần tự — dòng 1 xong tới dòng 2. Nhưng thực tế phần mềm luôn phải quyết định: nếu user chưa đăng nhập thì redirect login; nếu BMI < 18.5 thì phân loại "Gầy"; nếu số âm thì báo lỗi.
Bài này giải thích cơ chế rẽ nhánh trong Java: cú pháp if/else/else if, cách JVM thực thi, toán tử ba ngôi, và những bẫy tinh vi mà hầu hết tutorial bỏ qua.
1. Analogy — ngã ba đường
Đoạn code tuần tự giống con đường thẳng — bạn đi từ đầu đến cuối. if là ngã ba: tùy điều kiện mà rẽ nhánh này hay nhánh kia. else if là ngã tư nối tiếp — rẽ sai nhánh đầu thì thử nhánh tiếp theo. else là đường chốt cuối — mọi nhánh khác đều đổ về đây.
| Đời thường | if / else if / else |
|---|---|
| Ngã ba, chọn rẽ trái nếu có biển "Bệnh viện" | if (hasHospitalSign) |
| Ngã tư tiếp theo, chọn rẽ phải nếu có biển "Trường học" | else if (hasSchoolSign) |
| Không có biển nào — đi thẳng về nhà | else |
if kiểm tra điều kiện đúng/sai. Đúng → chạy block của if. Sai → bỏ qua, thử nhánh else if kế tiếp. Không nhánh nào đúng → rơi vào else. JVM chỉ chạy đúng 1 nhánh trong chuỗi.
2. Cú pháp cơ bản
int score = 75;
if (score >= 90) {
System.out.println("Excellent");
} else if (score >= 80) {
System.out.println("Good");
} else if (score >= 65) {
System.out.println("Fair");
} else if (score >= 50) {
System.out.println("Pass");
} else {
System.out.println("Fail");
}
Với score = 75, JVM xét lần lượt từ trên xuống rồi dừng ở điều kiện khớp đầu tiên. Hai ngưỡng phía dưới không phải "xét rồi trượt" — chúng không được chạm tới lần nào:

Hệ quả thực dụng: thứ tự các điều kiện là một phần của logic. Đảo score >= 50 lên đầu thì mọi ngưỡng cao hơn phía dưới thành code chết — không compile error, không warning, chỉ là điểm 95 cũng ra "Pass".
Điều kiện bên trong if (...) phải là boolean. Đây là điểm khác C/C++ — Java không cho if (5) hay if (ptr).
int x = 5;
if (x) { ... } // COMPILE ERROR — x khong phai boolean
if (x != 0) { ... } // OK — bieu thuc ra boolean
2.1 Khối lệnh (block) — nên dùng { } kể cả với 1 dòng
Java cho phép bỏ { } nếu body chỉ có 1 statement:
if (x > 0)
System.out.println("Positive");
Không làm thế. Lịch sử đã chứng kiến Apple "goto fail" bug (2014) — một dòng goto fail; thừa nằm "ngoài" if do không có { }, làm sập toàn bộ SSL validation của macOS/iOS trong nhiều tháng.
// Bug phong cach "goto fail"
if (isLoggedIn)
logInfo("login ok");
updateLastSeen(); // dong nay LUON chay, du chua dang nhap!
Thụt lề lừa mắt, nhưng updateLastSeen() không nằm trong if — nó là statement độc lập sau if.
✅ Luôn viết { } dù body chỉ 1 dòng. Không tốn thêm gì, phòng bug về sau.
3. Cơ chế bên dưới — JVM làm gì với if?
Java không có thực sự "nhánh if" ở cấp bytecode. Compiler biên dịch if thành instruction nhảy có điều kiện — kiểm tra boolean rồi goto tới địa chỉ khác.
Ví dụ:
int x = 10;
if (x > 5) {
System.out.println("big");
} else {
System.out.println("small");
}
Xem bytecode với javap -c:
bipush 10
istore_1 // x = 10
iload_1
iconst_5
if_icmple L_ELSE // neu x <= 5 thi nhay toi L_ELSE
// nhanh if:
ldc "big"
invokevirtual println
goto L_END // nhay qua nhanh else
L_ELSE:
ldc "small"
invokevirtual println
L_END:
return
Chú ý: compiler đảo ngược điều kiện. Bạn viết if (x > 5), bytecode là if (x <= 5) goto else. Vì CPU nhánh xử lý "nhảy khi sai, fall-through khi đúng" rẻ hơn 1 lệnh so với "nhảy khi đúng".
Hiểu if compile thành goto có điều kiện giúp bạn hiểu tại sao branch prediction ảnh hưởng performance, tại sao switch với nhiều case có thể nhanh hơn chuỗi if/else if dài (tableswitch / lookupswitch — xem bài sau), và vì sao loop unrolling lại là technique tối ưu thật sự.
4. Short-circuit — && và || không đánh giá hết
if (obj != null && obj.isValid()) {
// an toan — neu obj == null, obj.isValid() KHONG duoc goi
}
&& và || là short-circuit operator:
A && B— nếuA=false, không đánh giáB, kết quảfalse.A || B— nếuA=true, không đánh giáB, kết quảtrue.
Đây là cơ chế giúp kiểm tra null an toàn, tránh NullPointerException:
// An toan
if (user != null && user.getAge() >= 18) { ... }
// Nguy hiem — neu user == null, user.getAge() nem NPE
if (user.getAge() >= 18 && user != null) { ... }
& và | (không short-circuit) là bitwise AND/OR với số nguyên, và là logical AND/OR đánh giá cả 2 vế với boolean. Ít khi muốn dùng cho điều kiện:
if (a != null & a.doSomething()) { ... } // SAI — a.doSomething() van chay khi a null
Dùng && và || cho mọi điều kiện, dù compiler cho phép & với boolean.
5. Toán tử ba ngôi ? : — if/else rút gọn cho biểu thức
Khi bạn cần chọn 1 trong 2 giá trị để gán hoặc return, ternary ngắn gọn hơn nhiều:
// Cach dai
String status;
if (score >= 50) {
status = "Pass";
} else {
status = "Fail";
}
// Cach ngan
String status = (score >= 50) ? "Pass" : "Fail";
Cú pháp: <dieu kien> ? <gia tri neu dung> : <gia tri neu sai>. Cả 2 giá trị phải cùng kiểu (hoặc có quan hệ kế thừa / autoboxing tương thích).
5.1 Khi nào dùng, khi nào tránh?
✅ Dùng khi chọn 1 trong 2 giá trị đơn giản:
int absX = (x >= 0) ? x : -x;
String label = (isVip) ? "VIP" : "Guest";
return (list == null) ? "[]" : list.toString();
❌ Tránh khi:
- Nested ternary 3+ tầng — đọc không ra:
// Anti-pattern String s = a ? (b ? "ab" : "a") : (c ? "c" : "none"); - Có side effect trong nhánh (gọi method thay đổi state):
// Kho debug x = cond ? doOne() : doTwo(); - Block có 2+ statement — ternary chỉ cho expression, không cho statement.
Integer i = null;
int x = (flag) ? i : 0; // NEM NullPointerException neu flag = true
Java thấy nhánh kia là int, nên unbox Integer i về int — nếu i = null, NPE ngay. Dễ sót vì compile không cảnh báo.
6. Dangling-else — else gắn với if nào?
if (a > 0)
if (b > 0)
System.out.println("both positive");
else
System.out.println("???");
else gắn với if gần nhất — tức if (b > 0). Thụt lề đánh lừa mắt, nhưng không đổi ngữ nghĩa.
Nếu muốn else gắn với if (a > 0), bắt buộc { }:
if (a > 0) {
if (b > 0) {
System.out.println("both positive");
}
} else {
System.out.println("a <= 0");
}
Lại thêm một lý do luôn dùng { }.
7. Pattern match với instanceof (Java 16+)
Trước Java 16, kiểm tra kiểu phải cast riêng:
// Style cu (truoc Java 16)
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
Từ Java 16 (JEP 394), bạn có thể bind biến ngay trong instanceof:
// Pattern matching (Java 16+)
if (obj instanceof String s) {
// s duoc bind tu dong, scope trong block if
System.out.println(s.length());
}
Thậm chí kết hợp với &&:
if (obj instanceof String s && s.length() > 0) {
// s scope trong bieu thuc && ben phai va trong block if
System.out.println(s.charAt(0));
}
Ở module OOP / Pattern Matching chúng ta sẽ đào sâu thêm; giờ đủ để dùng.
8. Nested if sâu — dấu hiệu cần refactor
// Anti-pattern — pyramid of doom
if (user != null) {
if (user.isActive()) {
if (user.getPlan() != null) {
if (user.getPlan().isPremium()) {
// lam gi do sau 4 tang nested
}
}
}
}
Guard clause (early return) làm phẳng code, dễ đọc hơn:
void process(User user) {
if (user == null) return;
if (!user.isActive()) return;
if (user.getPlan() == null) return;
if (!user.getPlan().isPremium()) return;
// toan bo logic chinh o day, khong bi thut le sau
}
Đảo điều kiện và return sớm mỗi khi không hợp lệ. "Happy path" nằm ở cuối, không bị thụt lề.
Nếu nested if quá 3 tầng, dừng tay refactor ngay: guard clause, Optional, hoặc tách method nhỏ. Reader trả lương cho bạn là bạn kia (hoặc chính bạn 6 tháng sau).
9. Pitfall tổng hợp
❌ Nhầm 1: if (x = 5) — gán thay vì so sánh.
✅ Java chặn trường hợp này với kiểu non-boolean (compile error), nhưng với boolean thì qua lọt: if (flag = true) compile ok, luôn đúng → bug khó thấy. Dùng ==: if (x == 5).
❌ Nhầm 2: So sánh String bằng ==.
if (status == "ACTIVE") { ... } // lam viec voi literal, vo voi String tu Scanner
✅ Luôn "ACTIVE".equals(status) — đặt literal bên trái để null-safe.
❌ Nhầm 3: Quên { } rồi thêm dòng thứ 2.
if (retry)
logInfo("retry");
callApi(); // LUON chay du retry = false
✅ Luôn { }.
❌ Nhầm 4: Ternary với kiểu khác nhau gây auto-conversion bất ngờ.
Object o = flag ? 1 : "text"; // ca 2 nhanh autobox / wrap thanh Object
int x = flag ? 1 : 1.5; // x thanh double, loi compile neu kho gan
✅ Đảm bảo 2 nhánh cùng kiểu hoặc cast rõ ràng.
10. 📚 Deep Dive Oracle
Spec / reference chính thức:
- JLS §14.9 — The if Statement — cú pháp, quy tắc dangling-else.
- JLS §15.25 — Conditional Operator ? : — kiểu của biểu thức ternary, rules autoboxing/numeric promotion.
- JLS §15.24 — Conditional-Or / §15.23 — Conditional-And — quy tắc short-circuit.
- JEP 394 — Pattern Matching for instanceof — final trong Java 16.
Ghi chú: JLS quy định dangling-else gắn với if gần nhất để tránh ambiguity — hầu hết ngôn ngữ C-family đồng thuận. JEP 394 giới thiệu "pattern variable" — scope khá tinh tế, worth đọc khi bạn viết nhiều code ADT-style.
11. Tóm tắt
if/else if/elserẽ nhánh theo điều kiệnboolean. Java chỉ nhận boolean, không nhậnint != 0như C.- Luôn dùng
{ }kể cả cho body 1 dòng — phòng "goto fail" class of bugs. &&và||là short-circuit — dùng chúng để null-check an toàn.- Ternary
? :tốt cho chọn 1 trong 2 giá trị. Nested / có side effect thì nên trả vềif/else. - Bytecode của
iflà instruction nhảy có điều kiện — compiler đảo ngược điều kiện để tối ưu fall-through. - Nested
if3+ tầng → refactor thành guard clause. - Java 16+:
instanceof T sbind biến luôn, không cần cast rời.
12. Tự kiểm tra
- Q1Đoạn sau in gì và vì sao?
int x = 5; if (x > 0) System.out.println("a"); System.out.println("b"); System.out.println("c"); - Q2Vì sao
if (user != null && user.getAge() >= 18)an toàn, còn đảo ngược lại thì không? - Q3Viết ternary tính giá trị tuyệt đối của
int x. Sau đó giải thích tại sao cách này không an toàn vớiInteger.MIN_VALUE. - Q4Khi nào ternary
? :là lựa chọn tốt hơnif/else, và khi nào ngược lại? - Q5Đoạn sau compile không? Vì sao?
int x = 10; if (x) { System.out.println("non-zero"); }
Bài tiếp theo: switch — từ statement cổ điển đến expression hiện đại
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.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên