Switch case Java: Làm chủ Switch Expression trong Java

Trong lập trình Java, các cấu trúc điều khiển là yếu tố then chốt quyết định luồng hoạt động của chương trình. Bài viết này sẽ đi sâu vào một trong những cấu trúc mạnh mẽ nhất để xử lý các quyết định dựa trên nhiều điều kiện: câu lệnh switch. Chúng ta sẽ cùng nhau khám phá switch case Java truyền thống, tìm hiểu cách nó khắc phục sự phức tạp và khó quản lý của chuỗi if-else if-else kéo dài.

Đọc bài viết để hiểu thêm về:

  • Khi nào sử dụng Switch Case?
  • Switch Case Java truyền thống: bao gồm các cú pháp, các kiểu dữ liệu hỗ trợ và cách hoạt động
  • Switch Expression (Từ Java version 14)
  • Mẹo giúp code sạch hơn

Switch Case là gì? Khi nào sử dụng Switch Case?

Cấu trúc switch case là một lệnh điều khiển luồng, được thiết kế để so sánh một biến duy nhất với một tập hợp các giá trị cố định, cung cấp giải pháp gọn gàng hơn cho chuỗi if-else if-else kéo dài. 

Cấu trúc switch case không phải là một giải pháp hoàn hảo cho mọi tình huống điều kiện, nhưng nó thực sự tỏa sáng trong một số trường hợp cụ thể. Việc hiểu rõ ưu điểm của nó sẽ giúp bạn viết code Java hiệu quả và dễ bảo trì hơn.

Đọc chi tiết: Các câu lệnh trong Java: Chi tiết từ cơ bản đến nâng cao

So sánh switch case và if-else if-else

Việc lựa chọn giữa switchif-else phụ thuộc vào bản chất của điều kiện bạn muốn kiểm tra.

Tiêu chíSwitch case if-else
Loại so sánhSo sánh một biến duy nhất với nhiều giá trị cố định (so sánh bằng ==).Cho phép so sánh phức tạp hơn (ví dụ: >=, <=, &&, `)
Kiểu dữ liệuHỗ trợ các kiểu dữ liệu cơ bản như int, char, byte, short, và từ Java 7 trở đi là String và kiểu enum.Hỗ trợ so sánh mọi kiểu dữ liệu, bao gồm cả các đối tượng (non-primitive types) phức tạp.
Độ rõ ràngMã nguồn thường ngắn gọn và dễ đọc hơn khi có nhiều hơn ba hoặc bốn điều kiện.Dễ trở nên phức tạp và khó quản lý khi có quá nhiều khối else if lồng nhau.
Hiệu suấtCó thể có hiệu suất tốt hơn trong một số trường hợp vì trình biên dịch Java có thể tối ưu hóa thành bảng nhảy (jump table) để truy cập trực tiếp giá trị.Phải kiểm tra từng điều kiện một theo trình tự, có thể chậm hơn nếu điều kiện đúng nằm ở cuối chuỗi.

Khi nào nên dùng switch case?

Bạn nên ưu tiên sử dụng switch case khi:

  • So sánh một biến với nhiều giá trị cố định: Đây là trường hợp sử dụng lý tưởng. Ví dụ, bạn cần xử lý các hành động khác nhau dựa trên mã ngày trong tuần (1 đến 7) hoặc mã lỗi (200, 404, 500).
    Ví dụ: Kiểm tra giá trị của một biến thang để xác định số ngày trong tháng đó.
  • Cần code ngắn gọn và dễ đọc: Khi chuỗi có nhiều if else trở nên dài dòng và khó theo dõi, switch sẽ là một giải pháp trực quan hơn.

Khi nào nên dùng if-else?

Bạn nên dùng cấu trúc if-else khi:

  • So sánh phức tạp: Bạn cần kiểm tra các điều kiện về phạm vi (ví dụ: diem >= 50), kết hợp nhiều điều kiện (&& hoặc ||), hoặc so sánh không bằng (!=).
  • So sánh đối tượng: Khi bạn cần kiểm tra sự bằng nhau hoặc các tính chất của các đối tượng (ngoài Stringenum), đặc biệt là khi dùng phương thức equals().

Tóm lại, switch case là công cụ tối ưu cho việc chọn một trong nhiều đường đi dựa trên giá trị chính xác của một biến, giúp mã nguồn của bạn trở nên gọn gàng và có khả năng được tối ưu hóa tốt hơn về mặt hiệu suất.

Switch Case truyền thống

Switch case truyền thống là nền tảng mà mọi lập trình viên Java đều cần nắm vững trước khi chuyển sang các cấu trúc hiện đại hơn.

Cú pháp cơ bản

Cú pháp của câu lệnh switch truyền thống rất đơn giản và dễ hiểu, cho phép bạn chỉ định các khối mã khác nhau để thực thi dựa trên giá trị của một biến.

switch (biểu_thức) {
    case giá_trị_1:
        // Khối lệnh 1
        break; // Tùy chọn, nhưng rất quan trọng!
    case giá_trị_2:
        // Khối lệnh 2
        break;
    // ...
    default:
        // Khối lệnh mặc định (nếu không khớp case nào)
        // break ở đây là không cần thiết, nhưng cũng không sai
}
  • switch (biểu_thức): Biểu thức được đặt trong ngoặc đơn sẽ được đánh giá. Giá trị của biểu thức này sẽ được so sánh lần lượt với các case.
  • case giá_trị: Đây là nhãn (label) để so sánh. Nếu giá trị của biểu thức khớp với giá_trị này, luồng thực thi sẽ nhảy đến khối lệnh này và tiếp tục chạy từ đó.
  • default: Khối lệnh này là tùy chọn. Nếu giá trị của biểu thức không khớp với bất kỳ case nào, luồng thực thi sẽ nhảy đến khối default (nếu có).

Các kiểu dữ liệu hỗ trợ

Không phải mọi kiểu dữ liệu đều có thể được sử dụng trong câu lệnh switch. Java giới hạn các kiểu dữ liệu có thể dùng để làm biểu thức trong switch() như sau:

  • Kiểu số nguyên: byte, short, char, và int.
  • Kiểu đối tượng tương ứng (Wrapper Types): Byte, Short, Character, và Integer.
  • Kiểu nâng cao (từ Java 5 và 7):
    • Enum (từ Java 5): Thường được sử dụng nhất để so sánh các hằng số.
    • String (từ Java 7): Cho phép so sánh chuỗi một cách trực tiếp.

Lưu ý: Các kiểu dữ liệu như long, float, double, và boolean không được phép sử dụng trong câu lệnh switch.

Khái niệm quan trọng: break và Fall-through

Cách hoạt động của switch case được xác định bởi hai khái niệm then chốt này:

Vai trò của break

Từ khóa break có vai trò tối quan trọng: nó ngăn chặn hiện tượng Fall-through.

  • Khi Java tìm thấy case khớp, nó bắt đầu thực thi các lệnh trong khối đó.
  • Khi gặp từ khóa break, nó ngay lập tức thoát ra khỏi toàn bộ câu lệnh switch.

Hiện tượng Fall-through

Fall-through là hành vi mặc định của switch case khi không có từ khóa break.

Sau khi một case khớp và được thực thi, nếu không có break, luồng chương trình sẽ tiếp tục chạy xuống các khối case kế tiếp, bất kể các case đó có khớp với giá trị ban đầu hay không, cho đến khi nó gặp break hoặc kết thúc switch.

Hiện tượng Fall-through thường được sử dụng có chủ đích để gộp nhiều case lại với nhau khi chúng có cùng khối lệnh cần thực thi.

int day = 6; // Thứ Bảy
String dayType;

switch (day) {
    case 6: // Thứ Bảy
    case 7: // Chủ Nhật
        dayType = "Cuối tuần";
        break; // Thoát khỏi switch sau khi gán
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        dayType = "Ngày làm việc";
        break;
    default:
        dayType = "Không hợp lệ";
}
// Kết quả: dayType = "Cuối tuần"

Chú ý: Nếu bạn vô tình quên từ khóa break trong một case mà không có ý định gộp, đó là một lỗi logic nghiêm trọng vì chương trình sẽ thực thi các khối lệnh của case tiếp theo, dẫn đến kết quả sai lệch và rất khó để gỡ lỗi.

Quy tắc Quan trọng

  • Giá trị case phải là hằng số: Giá trị sau từ khóa case phải là một hằng số đã biết tại thời điểm biên dịch (compile-time constant). Nó không thể là một biến (variable) hoặc một biểu thức phức tạp.
  • Giá trị case không được trùng lặp: Mỗi giá trị case trong cùng một khối switch phải là duy nhất.
  • Khối default là tùy chọn: Không bắt buộc phải có khối default, nhưng nên thêm vào để xử lý các trường hợp không lường trước được.

Switch Expression (Từ Java version 14)

Để giải quyết các vấn đề cố hữu của switch case truyền thống, Java đã giới thiệu Switch Expression (Biểu thức Switch) bắt đầu từ phiên bản Java 14, giúp mã nguồn trở nên gọn gàng, an toàn và có tính biểu cảm cao hơn.

Switch truyền thống có một số nhược điểm chính:

  1. Dễ bị lỗi Fall-through: Việc quên từ khóa break là nguyên nhân phổ biến gây ra lỗi logic.
  2. Không thể dùng làm biểu thức: switch case cũ chỉ là một câu lệnh (statement), không thể trực tiếp trả về một giá trị để gán vào biến.
  3. Dài dòng: Cú pháp cũ yêu cầu lặp lại break cho hầu hết các case.

Switch Expression giải quyết triệt để các vấn đề này bằng cách cho phép switch hoạt động như một biểu thức có thể tính toán và trả về một giá trị.

Cú pháp mới với toán tử Mũi tên (->)

Điểm khác biệt lớn nhất là sự xuất hiện của toán tử mũi tên -> thay cho dấu hai chấm :

Cú pháp Truyền thống: case giá trị:

Switch Expression: case giá_trị ->

Lợi ích của cú pháp ->

  • Tự động loại bỏ Fall-through: Với cú pháp mũi tên, chỉ có khối lệnh sau -> được thực thi. Bạn không cần dùng từ khóa break nữa vì hệ thống tự động thoát khỏi switch sau khi thực thi khối lệnh.
  • Trả về giá trị đơn giản: Nếu khối lệnh chỉ có một dòng, dòng đó tự động được coi là giá trị trả về của toàn bộ biểu thức switch.

Switch Expression

Bạn có thể gán trực tiếp kết quả của Switch Expression vào một biến, giúp loại bỏ việc phải khai báo biến trước và gán giá trị trong từng case.

// Ví dụ: Gán trực tiếp kết quả vào biến
int day = 3;
String dayType = switch (day) {
    case 6, 7 -> "Cuối tuần";      // Tự động trả về giá trị này
    case 1, 2, 3, 4, 5 -> "Ngày làm việc";
    default -> "Không hợp lệ";
};
// Kết quả: dayType = "Ngày làm việc"

Sử dụng từ khóa yield

Nếu một case cần thực hiện nhiều hơn một câu lệnh (ví dụ: in ra thông báo trả về giá trị), bạn sẽ sử dụng khối lệnh (dùng {}) và từ khóa yield để chỉ định giá trị trả về. yield đóng vai trò tương tự như return trong một phương thức, nhưng chỉ hoạt động bên trong Switch Expression.

int score = 95;
String grade = switch (score / 10) {
    case 10, 9 -> {
        System.out.println("Chúc mừng!"); // Câu lệnh 1
        yield "A";                      // Trả về giá trị
    }
    case 8 -> "B";
    default -> "C";
};
// Kết quả: In ra "Chúc mừng!" và grade = "A"

So sánh Switch case Truyền thống vs. Switch Expression

Dưới đây là bảng tổng hợp các điểm khác biệt cốt lõi, giúp bạn quyết định khi nào nên sử dụng Switch Expression (Java 14+) để viết code an toàn và gọn gàng hơn:

Tiêu chí So sánhSwitch case Truyền thốngSwitch expression (Java 14+)
Bản chấtCâu lệnh (Statement)Biểu thức (Expression)
Mục đíchThực thi hành độngTrả về giá trị trực tiếp
Cú pháp chínhDấu hai chấm :Toán tử mũi tên ->
Ngăn chặn Fall-throughBẮT BUỘC dùng từ khóa breakTự động loại bỏ (không cần break)
Linh hoạtDài dòng, dễ lỗi logicNgắn gọn, an toàn hơn
Trả về giá trị phức tạpKhôngSử dụng từ khóa yield

Mẹo giúp code sạch hơn khi dùng cấu trúc switch

Để đảm bảo mã nguồn của bạn không chỉ hoạt động mà còn dễ đọc, dễ bảo trì và theo kịp các tiêu chuẩn hiện đại của Java, hãy áp dụng các mẹo sau khi làm việc với cấu trúc switch:

Luôn ưu tiên Switch Expression (Java 14+)

Nếu dự án của bạn đang sử dụng Java 14 trở lên, hãy luôn ưu tiên sử dụng Switch Expression với toán tử mũi tên ->.

Nó loại bỏ nhu cầu sử dụng break, loại bỏ nguy cơ lỗi Fall-through vô ý, và cho phép bạn viết mã theo phong cách biểu thức (trả về giá trị trực tiếp), giúp code ngắn gọn hơn nhiều so với switch case truyền thống.

Sử dụng kiểu liệt kê (Enum) với switch

Đây là một trong những cách tốt nhất để làm sạch code và tăng tính an toàn kiểu dữ liệu (type safety).

  • Tính dễ đọc: Việc so sánh case với các tên Enum (ví dụ: case MUA_XUAN:) dễ đọc hơn nhiều so với việc so sánh với các hằng số số học (case 1:).
  • An toàn kiểu dữ liệu: Enum đảm bảo bạn chỉ đang xử lý các giá trị hợp lệ đã được định nghĩa.
  • Kiểm soát toàn diện: Trình biên dịch Java có thể cảnh báo bạn nếu bạn quên xử lý một case nào đó trong Enum (khi sử dụng switch expression), giúp đảm bảo bạn luôn bao quát mọi trường hợp.

Tránh các khối case quá dài hoặc phức tạp

Mục tiêu của switch case là điều khiển luồng dựa trên các giá trị cố định, không phải để chứa logic phức tạp.

Nguyên tắc: Nếu khối lệnh bên trong một case trở nên quá dài (ví dụ: nhiều hơn 5-7 dòng code) hoặc thực hiện nhiều tác vụ khác nhau, hãy trích xuất (Extract) khối logic đó thành một phương thức (method) riêng biệt.

Ví dụ: Thay vì viết toàn bộ logic xử lý đơn hàng trong case DANG_CHO:, bạn chỉ cần gọi case DANG_CHO -> xuLyDonHangDangCho(donHang);. Điều này giữ cho cấu trúc switch gọn gàng và tập trung vào vai trò điều phối luồng chính của nó.

Bằng cách tuân thủ các mẹo này, bạn sẽ biến các cấu trúc điều kiện phức tạp thành các đoạn mã rõ ràng, dễ bảo trì, thể hiện sự chuyên nghiệp trong phong cách lập trình Java của mình.

Các câu hỏi thường gặp về switch case Java

Switch case có thể sử dụng với kiểu long hoặc float/double không?

Không. Câu lệnh switch trong Java không hỗ trợ các kiểu dữ liệu long, float, hoặc double (cũng như boolean).

Lý do: Các giá trị case cần phải là hằng số nguyên hoặc các giá trị có thể được so sánh bằng nhau một cách rõ ràng và hiệu quả (như int, char, String, Enum). Kiểu float và double sử dụng biểu diễn dấu phẩy động, dễ gây ra sai số chính xác (precision issues) khi so sánh bằng ==, khiến chúng không phù hợp cho việc so sánh cố định của switch

Đọc chi tiết: Các kiểu dữ liệu trong Java A-Z: Định nghĩa và Cách sử dụng

Điều gì xảy ra nếu quên khối default?

Nếu bạn không có khối default và không có case nào khớp với giá trị của biểu thức switch:

  • Trong switch case truyền thống: Chương trình sẽ bỏ qua toàn bộ khối switch và tiếp tục thực thi các câu lệnh ngay sau nó. Không có lỗi phát sinh.
  • Trong switch expression (Java 14+): Nếu bạn không bao quát tất cả các trường hợp có thể xảy ra (ví dụ: xử lý tất cả các giá trị của một Enum hoặc tất cả các số nguyên có thể có), bạn phải cung cấp khối default. Nếu không, trình biên dịch sẽ báo lỗi vì switch expression phải đảm bảo trả về một giá trị trong mọi tình huống.

Có thể gộp nhiều case trong Switch Expression (->) không?

Có và nó rất dễ dàng. Khác với việc phải sử dụng Fall-through và bỏ break trong cấu trúc truyền thống, Switch Expression cho phép bạn liệt kê nhiều giá trị cách nhau bằng dấu phẩy.

// Cú pháp gọn gàng và an toàn
case 1, 3, 5 -> "Ngày lẻ";
case 2, 4, 6 -> "Ngày chẵn";

Khi nào thì nên dùng switch thay vì if-else?

Bạn nên dùng switch khi bạn đang so sánh một biến duy nhất với một tập hợp các giá trị cố định (như mã trạng thái, ký tự, hoặc giá trị Enum).

Bạn nên dùng if-else khi:

  1. Cần so sánh phạm vi (>=, <=).
  2. Cần kết hợp nhiều điều kiện (&&,||).
  3. Cần so sánh các đối tượng phức tạp (không phải String hoặc Enum).

Đọc chi tiết: Biến trong Java: Chi tiết hướng dẫn sử dụng đúng cách

Tổng kết

Qua bài viết này, bạn đã trang bị kiến thức toàn diện về switch trong Java. Chúng ta đã đi từ việc hiểu vai trò của switch case truyền thống (giải pháp thay thế cho if-else phức tạp) đến việc nắm bắt sức mạnh của Switch Expression hiện đại.

Đã đến lúc bạn nên thay đổi thói quen lập trình! Việc chuyển đổi sang sử dụng Switch Expression không chỉ giúp mã nguồn của bạn trở nên hiện đại mà còn loại bỏ một nguồn lỗi phổ biến trong lập trình Java.

TÁC GIẢ
Tien Tran
Tien Tran

iOS Developer

Có 4 năm kinh nghiệm trong lĩnh vực phát triển ứng dụng mobile, được chứng minh qua lịch sử làm việc ở các công ty lớn (VCCorp, KiotViet, Vega Fintech). Với đam mê tìm hiểu, nghiên cứu những kiến thức chuyên môn cần có của một lập trình viên mobile hiện nay như Swift, Objective C, Flutter, Kotlin,... Tiến mong muốn chia sẻ kinh nghiệm làm việc và truyền cảm hứng cho mọi người muốn theo đuổi con đường trở thành một nhà phát triển ứng dụng di động chuyên nghiệp trong tương lai.