Menu

Enum trong Java: tập hằng số có tên cố định

Enum trong Java là gì, cách khai báo, thêm thuộc tính và phương thức, dùng switch với enum, và vì sao enum tốt hơn một loạt hằng số int hay String.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Một tập giá trị có tên cố định

Có những giá trị chỉ có nghĩa khi thuộc một danh sách nhỏ, biết trước: các ngày trong tuần, các chất trong bộ bài, các trạng thái của một đơn hàng. Bạn có thể biểu diễn chúng bằng mã int (0 là đang chờ, 1 là đã gửi) hoặc chuỗi ("PENDING", "SHIPPED"), nhưng không gì ngăn người gọi truyền vào 7 hay "PNEDING". enum bịt lỗ hổng đó: chính kiểu dữ liệu chỉ cho phép những giá trị bạn đã khai báo.

Day là một kiểu hoàn toàn mới. Một biến kiểu Day chỉ có thể chứa đúng một trong bảy hằng số kia, hoặc null, ngoài ra không gì khác. Trình biên dịch từ chối Day d = "WEDNESDAY"; hay Day d = 2;. Sự đảm bảo đó chính là mục đích của enum.

Mỗi hằng số (Day.MONDAY và các hằng số khác) là một instance dùng chung duy nhất, chỉ được tạo một lần. Vì chỉ có đúng một MONDAY, bạn so sánh enum bằng == thay vì equals: chúng là cùng một đối tượng.

Dùng switch với enum

Enum kết hợp rất tự nhiên với switch. Bên trong switch, bạn thậm chí không cần ghi tên kiểu trước mỗi hằng số:

Bên trong switch, hãy viết case SATURDAY, không phải case Day.SATURDAY: Java tự suy ra kiểu. Lợi thế lớn so với switch trên chuỗi hay int là khi bạn thêm hằng số mới sau này, IDE và các kiểm tra tính đầy đủ có thể chỉ ra mọi switch quên xử lý nó.

Lặp qua mọi hằng số

Mỗi enum tự động có phương thức tĩnh values() trả về mọi hằng số theo thứ tự khai báo, cùng với ordinal() cho biết vị trí của từng hằng số, bắt đầu từ 0:

name() trả về tên định danh của hằng số dưới dạng String, còn values() rất hợp để dựng menu hoặc lặp qua các lựa chọn. Một lưu ý: đừng lưu ordinal() vào bất cứ chỗ nào lâu dài. Nếu sau này ai đó sắp xếp lại các hằng số, các con số sẽ dịch chuyển và dữ liệu đã lưu bị hỏng. Hãy coi ordinal() là chi tiết cài đặt, không phải một ID ổn định.

Enum là lớp thật sự: thuộc tính và phương thức

Đây là chỗ enum của Java vượt xa kiểu enum "số nguyên có tên" của các ngôn ngữ khác. Mỗi hằng số có thể mang dữ liệu, truyền qua constructor, và enum có thể có phương thức. Bạn liệt kê các hằng số trước, cho mỗi hằng số đối số constructor của nó, rồi khai báo thuộc tính, constructor và phương thức sau dấu chấm phẩy.

Hãy để ý cấu trúc: các hằng số đứng đầu và kết thúc bằng dấu chấm phẩy, rồi mới đến phần còn lại của lớp. Constructor ngầm định là private: bạn không bao giờ viết được new Planet(...), vì chỉ những instance khai báo ở đầu mới được phép tồn tại. Các thuộc tính final khiến mỗi hằng số bất biến, đúng như điều bạn muốn với các singleton dùng chung.

Từ chuỗi sang enum và ngược lại

Để chuyển một chuỗi thành hằng số tương ứng, dùng phương thức valueOf được tạo tự động:

valueOf so khớp tên hằng số chính xác, phân biệt cả hoa thường, và ném IllegalArgumentException nếu không tìm thấy. Đó là lỗi hay gặp: "shipped" không khớp với SHIPPED. Khi phân tích dữ liệu người dùng nhập hoặc dữ liệu từ bên ngoài, hãy chuẩn hóa chữ hoa thường trước (input.toUpperCase()) và bọc lời gọi trong try/catch, nếu không chương trình sẽ dừng ngay ở giá trị sai đầu tiên.

Hành vi riêng cho từng hằng số

Đôi khi mỗi hằng số cần hành xử khác nhau, không chỉ chứa dữ liệu khác nhau. Bạn có thể cho enum một phương thức trừu tượng và để mỗi hằng số tự cài đặt nó trong một thân nhỏ:

Cách này đôi khi được gọi là mẫu "phương thức riêng theo hằng số" (constant-specific method). Nó thay một switch dài dòng trên hằng số bằng hành vi gắn trực tiếp vào từng hằng số: thêm một phép toán mới thì trình biên dịch buộc bạn định nghĩa apply cho nó, nên bạn không thể bỏ sót trường hợp nào.

Khi nào nên dùng enum

Hãy dùng enum bất cứ khi nào một giá trị tự nhiên thuộc một tập nhỏ, cố định, biết trước lúc biên dịch:

  • Các trạng thái trong một quy trình (PENDING, SHIPPED, DELIVERED).
  • Danh mục, chế độ hoặc loại (READ, WRITE, EXECUTE).
  • Bất cứ chỗ nào bạn định khai báo một cụm hằng số public static final int: enum cho bạn cùng các giá trị có tên đó, kèm theo an toàn kiểu, toString dễ đọc và hỗ trợ switch miễn phí.

Đừng dùng enum cho một tập mở hoặc chỉ biết lúc chạy (tên người dùng, danh sách quốc gia lấy từ cơ sở dữ liệu). Enum được cố định lúc biên dịch; nếu tập giá trị thay đổi khi chương trình đang chạy, bạn cần một collection thông thường.

Tiếp theo: generics

Bạn đã thấy values() trả về Planet[] và các trang trước dùng List<Shape>. Cú pháp <...> đó là generics, cách Java cho phép viết một lớp hay phương thức dùng được với nhiều kiểu mà vẫn giữ trọn an toàn kiểu. Tiếp theo, chúng ta sẽ tìm hiểu List<String> và Map<K, V> thực sự hoạt động thế nào và cách tự viết kiểu generic của riêng bạn.

Câu hỏi thường gặp

Enum trong Java là gì?

enum là một kiểu đặc biệt mà giá trị chỉ có thể là một trong một tập hằng số có tên, cố định, như Day.MONDAY hay Status.ACTIVE. Mỗi hằng số là một instance duy nhất, được tạo sẵn của enum. Enum mang lại an toàn kiểu: một phương thức nhận Day chỉ có thể nhận một ngày thật, không bao giờ nhận một chuỗi gõ sai hay một int ngoài phạm vi.

Enum trong Java có thể có thuộc tính và phương thức không?

Có. Enum là một lớp đầy đủ. Bạn có thể truyền đối số constructor cho từng hằng số, lưu chúng trong các thuộc tính private final và thêm phương thức dùng các thuộc tính đó. Ví dụ MERCURY(3.3e23, 2.4e6) truyền khối lượng và bán kính vào constructor của enum, và phương thức surfaceGravity() có thể tính toán từ các thuộc tính này.

values() và valueOf() của enum trong Java khác nhau thế nào?

values() trả về một mảng chứa mọi hằng số của enum theo thứ tự khai báo, rất tiện để lặp qua từng lựa chọn. valueOf("NAME") làm ngược lại: nó tìm hằng số có tên khớp chính xác với chuỗi và ném IllegalArgumentException nếu không có hằng số nào khớp. Cả hai đều do trình biên dịch tự động tạo ra.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu