switch — từ statement cổ điển đến expression hiện đại
Khám phá switch trong Java: phân biệt switch statement cổ điển có fall-through và switch expression hiện đại từ Java 14, cùng cơ chế bytecode tableswitch.
TL;DR: Lệnh switch là cấu trúc tra cứu giá trị hiệu năng cao. Phiên bản cổ điển (statement) tiềm ẩn lỗi nguy hiểm do cơ chế fall-through khi quên từ khóa break. Từ Java 14, switch expression với cú pháp mũi tên -> loại bỏ hoàn toàn fall-through, hỗ trợ trả về giá trị trực tiếp hoặc qua từ khóa yield, đồng thời kiểm tra tính đầy đủ (exhaustive). Ở tầng bytecode, JVM tối ưu hóa các hằng số liên tục thành tableswitch với độ phức tạp $O(1)$ và các hằng số phân tán thành lookupswitch với độ phức tạp $O(\log n)$.
Khi bạn có 5–10 nhánh so sánh cùng một biến với các hằng số, chuỗi if/else if dài lê thê. switch được sinh ra cho đúng pattern đó. Nhưng switch cổ điển có cái bẫy khó quên — fall-through — đã gây không ít lỗi production nổi tiếng.
Bài này giải thích switch từ phiên bản cổ điển (có fall-through), đến switch expression (Java 14+) an toàn hơn và trả được giá trị (rất phổ biến khi kết hợp với Enum type-safe), đến pattern matching (Java 21). Kèm cơ chế bytecode giải thích vì sao switch có thể nhanh hơn chuỗi if.
1. Analogy máy bán nước tự động mô tả cấu trúc switch như thế nào?
Bạn bỏ đồng xu, nhấn mã: A1 cho Coca, A2 cho Pepsi, B1 cho nước lọc, mặc định báo "hết hàng". Máy có bảng tra cứu từ mã đến hành động, không phải loạt "nếu A1 thì..., nếu A2 thì..., nếu B1 thì...".
| Đời thường | switch |
|---|---|
Mã A1, A2, B1... | case "A1":, case "A2": |
| Không mã nào khớp → "hết hàng" | default: |
| Máy tra bảng nhanh, không đọc từng mã | JVM có thể dùng tableswitch / lookupswitch |
switch là bảng tra cứu — compiler biết trước tập giá trị hằng để so, nên có thể sinh instruction nhảy trực tiếp theo index thay vì so sánh tuần tự.
2. Cú pháp switch statement cổ điển hoạt động ra sao và tiềm ẩn bẫy gì?
int day = 3;
String name;
switch (day) {
case 1:
name = "Mon";
break;
case 2:
name = "Tue";
break;
case 3:
name = "Wed";
break;
case 4:
name = "Thu";
break;
case 5:
name = "Fri";
break;
case 6:
case 7:
name = "Weekend";
break;
default:
name = "Invalid";
}
Điểm đặc biệt:
- Biểu thức sau
switch (...)phải là kiểubyte,short,int,char,String(Java 7+), hoặcenum. Không nhậnlong,double,float,boolean. casephải là hằng số compile-time. Không đượccase someVar.- Thiếu
break→ fall-through sang case tiếp theo. Đôi khi hữu ích (case 6, 7 cùng xử lý "Weekend"), nhưng đa số là bug.
2.1 Fall-through — bẫy kinh điển
int status = 1;
switch (status) {
case 1:
System.out.println("active");
// QUEN break
case 2:
System.out.println("pending");
break;
case 3:
System.out.println("closed");
break;
}
Output: active rồi pending. Quên break ở case 1 → thực thi tiếp tục "rơi xuống" case 2.
Điểm dễ trượt: case 2: không phải một nhánh riêng được "chọn" — nó chỉ là một nhãn đánh dấu vị trí trong dòng chảy. JVM nhảy tới nhãn khớp rồi chạy thẳng xuống cho tới khi gặp break, kể cả khi phải băng qua nhãn của case khác:

Java lấy nguyên semantics này từ C — C giữ nó vì đôi khi tiện cho state machine / jump table. Trong Java 90% trường hợp là bug, vì vậy Java 14 giới thiệu cú pháp mới không có fall-through mặc định.
Thử ngẫmmột service xử lý order status dùng switch cổ điển, case ACTIVE thiếu break nên log in luôn cả dòng của PENDING. Sửa bằng thêm break, hay đổi hẳn sang switch expression — khác nhau ở điều gì?
3. Cú pháp switch expression từ Java 14 có ưu điểm gì vượt trội?
Cú pháp mới (case L ->) không còn fall-through, và switch trở thành expression trả giá trị:
int day = 3;
String name = switch (day) {
case 1 -> "Mon";
case 2 -> "Tue";
case 3 -> "Wed";
case 4 -> "Thu";
case 5 -> "Fri";
case 6, 7 -> "Weekend";
default -> "Invalid";
};
Lợi ích:
- Không fall-through — mỗi
case L ->là 1 block độc lập, không cầnbreak. - Trả giá trị — gán trực tiếp vào biến, không cần khai báo biến trước rồi gán trong từng case.
- Case gộp —
case 6, 7 -> ...ngắn gọn. - Exhaustive check với
enum/sealed— compiler báo lỗi nếu thiếu case.
3.1 Block body với yield
Khi case cần nhiều statement trước khi "trả giá trị", dùng { } và yield:
String desc = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> {
log("Weekend hit");
yield "Weekend";
}
default -> throw new IllegalArgumentException("Day " + day);
};
yield = "trả giá trị ra ngoài switch". Khác return — return thoát khỏi method, yield chỉ thoát khỏi switch.
3.2 Exhaustive check với enum
enum Status { ACTIVE, PENDING, CLOSED }
String display = switch (status) {
case ACTIVE -> "Hoat dong";
case PENDING -> "Cho";
case CLOSED -> "Da dong";
// khong can default — compiler da biet 3 case phu het enum
};
Nếu enum thêm value mới (BANNED), code trên compile error — buộc bạn update. Đây là power của type system: refactor an toàn, không sót case.
- Code mới → luôn dùng switch expression (
case L ->). An toàn hơn, ngắn hơn. - Code cũ có sẵn → giữ nguyên statement nếu không gây vấn đề; migration khi refactor.
- Cần fall-through thật sự (hiếm) → statement cổ điển mới có. Nhưng nên refactor thành
case A, B ->nếu có thể.
Thử ngẫmenum Status thêm case BANNED mà switch expression cũ không viết default — compiler chặn bạn ngay. Nếu bạn từng thêm default -> null cho gọn, cái chặn đó còn tác dụng không?
4. Cơ chế tableswitch và lookupswitch ở tầng bytecode hoạt động ra sao?
JVM có 2 instruction đặc biệt cho switch:
tableswitch— dùng khi cáccaselà số nguyên liên tiếp / gần liên tiếp. JVM nhảy theo offset trong bảng. O(1).lookupswitch— dùng khi cáccasethưa thớt (ví dụ 1, 100, 1000). JVM tra bảng sorted key. O(log n).
Compiler chọn tự động. Ví dụ switch (day) với case 1–7:
tableswitch {
1: GOTO case1
2: GOTO case2
...
7: GOTO case7
default: GOTO defaultLabel
}
tableswitch tiêu tốn memory (bảng dense) nhưng nhảy trực tiếp — nhanh hơn chuỗi if/else if O(n).
// "switch 1000 case" van O(1) neu dung tableswitch
// 1000 if/else if la O(n) — kiem tra tu dau den cuoi
Bytecode dành riêng cho switch (tableswitch, lookupswitch) cho JIT tối ưu thêm (branch table, jump table đích thật). Class kiểu Character.UnicodeBlock hoặc java.util.TimeZone có switch rất dài — chọn switch không chỉ vì cú pháp mà cả vì performance.
4.1 switch với String — hash + equals
switch nhận String từ Java 7, nhưng String không phải số nguyên. Compiler biến đổi:
switch (s) {
case "Mon": ...
case "Tue": ...
}
Thành 2 tầng:
switch (s.hashCode())— chọn case ứng viên.- Bên trong mỗi case,
s.equals("Mon")để xác minh (phòng hash collision).
Kết quả: O(1) trung bình, correctness đảm bảo qua equals. Bạn không cần tự tối ưu — compiler đã lo.
5. Tính năng Pattern Matching cho switch trong Java 21 hoạt động như thế nào?
Java 21 final hoá pattern matching trong switch (JEP 441), cho phép case theo kiểu + điều kiện:
static String describe(Object obj) {
return switch (obj) {
case Integer i when i > 0 -> "positive int: " + i;
case Integer i -> "non-positive int: " + i;
case String s when s.isEmpty() -> "empty string";
case String s -> "string of len " + s.length();
case int[] arr -> "int array len " + arr.length;
case null -> "null";
default -> "unknown";
};
}
Đặc điểm:
- Pattern
Type varbind biến (giốnginstanceof Type var). when <condition>thêm guard — case khớp khi cả pattern và condition đúng.case nullcho phép matchnulltường minh (trước Java 21,switchném NPE với null).- Exhaustive với
sealed class— compiler đảm bảo phủ hết subtype.
Trước Java 21 bạn sẽ viết if/else if/else với instanceof; giờ switch lo luôn. Chi tiết ở module OOP nâng cao.
Thử ngẫmđoạn service cũ bọc switch trong try/catch để nuốt NullPointerException khi tham số null. Chuyển sang case null -> của Java 21, đoạn try/catch đó còn cần không?
6. Khi nào nên dùng switch và khi nào nên dùng chuỗi if else?
| Tình huống | Nên dùng |
|---|---|
| So biến với nhiều hằng số (>3 nhánh) cùng kiểu | switch |
| Điều kiện phức tạp (range, biểu thức boolean) | if/else if |
So sánh double, float, long | if/else if (switch không hỗ trợ) |
| Cần trả giá trị, gán biến | switch expression |
| Enum với tất cả case | switch expression (exhaustive check) |
| Kiểm tra kiểu run-time (polymorphism) | Java 21: switch pattern matching; trước đó: instanceof + if |
7. Các bẫy pitfall thường gặp nhất khi sử dụng switch là gì?
❌ Nhầm 1: Quên break trong switch statement.
case 1:
x = 10;
// quen break — chay tiep case 2
case 2:
x = 20;
break;
✅ Dùng switch expression (case L ->) — không fall-through mặc định.
❌ Nhầm 2: Tưởng case nhận biến.
int threshold = 10;
switch (x) {
case threshold: ... // COMPILE ERROR — case can constant
}
✅ case chỉ nhận hằng số compile-time: literal, final variable được biết trước lúc compile, enum value.
❌ Nhầm 3: Switch trên String null (Java < 21).
String s = null;
switch (s) { ... } // nem NullPointerException
✅ Kiểm tra null trước; hoặc dùng Java 21 có case null.
❌ Nhầm 4: Dùng switch expression thiếu default cho kiểu không phải enum/sealed.
int kind = readKind();
String label = switch (kind) {
case 1 -> "A";
case 2 -> "B";
// COMPILE ERROR — kind la int, khong exhaustive, can default
};
✅ Thêm default -> hoặc default -> throw new IllegalStateException(...).
8. Tài liệu Deep Dive Oracle quy định gì về cấu trúc switch?
Spec / reference chính thức:
- JLS §14.11 — The switch Statement — cú pháp, fall-through, exhaustiveness.
- JLS §15.28 — switch Expressions — rule
yield, exhaustive check. - JEP 361 — Switch Expressions — final trong Java 14.
- JEP 441 — Pattern Matching for switch — final trong Java 21.
- JVMS §3.10 — Compiling Switches —
tableswitchvslookupswitchở cấp bytecode.
Ghi chú: JEP 361 là bước ngoặt — switch từ statement-only thành expression, mở đường cho pattern matching. JVMS §3.10 giải thích chi tiết cách compiler chọn instruction nào cho density khác nhau — worth đọc khi bạn viết code hot-path.
9. Điểm cốt lõi cần nhớ về switch trong Java là gì?
switchhoạt động như bảng tra cứu giá trị, tối ưu bytecode bằngtableswitch(O(1)) hoặclookupswitch(O(log n)).- Switch statement cổ điển dễ sinh lỗi vì cơ chế fall-through ngầm định khi thiếu lệnh
break. - Switch expression (
case L ->, Java 14+) là chuẩn mặc định cho code mới nhờ an toàn fall-through và exhaustive check. - Từ Java 21, pattern matching cho phép switch kiểm tra trực tiếp kiểu dữ liệu, guard condition
when, và xử lý tường minhcase null.
10. Tự kiểm tra kiến thức về cấu trúc switch như thế nào?
- Q1Đoạn sau in gì?
int x = 2; switch (x) { case 1: System.out.println("A"); case 2: System.out.println("B"); case 3: System.out.println("C"); break; default: System.out.println("D"); } - Q2Viết lại đoạn trên bằng switch expression trả về
String. Sau đó giải thích 2 ưu điểm so với bản statement. - Q3Vì sao không switch được trên
doublehaylong? - Q4Khi nào compiler sinh
tableswitch, khi nào sinhlookupswitch? - Q5Đoạn sau compile không?
enum Color { RED, GREEN, BLUE } Color c = Color.RED; String hex = switch (c) { case RED -> "#FF0000"; case GREEN -> "#00FF00"; };
Bài tiếp theo: while và do-while — vòng lặp có điều kiện
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.
if / else / else if — rẽ nhánh theo điều kiện
Bài tiếpwhile và do-while — Lặp khi chưa biết trước số lần
Lộ trình Junior Java Backend Developer
Chặng 2/21 · Lập trình từ số 0 với Java — bài 21/32 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này