Java Foundations
18/39
Bài 18 / 39~14 phútCú pháp Java & Kiểu dữ liệuLộ trình · chặng 02/21Miễn phí… lượt xem

Enum — tập giá trị cố định, type-safe thay cho magic number

Tìm hiểu kiểu dữ liệu enum trong Java: tính an toàn type-safe thay thế magic number, switch exhaustive, khai báo field và method, cùng các pitfall với ordinal.

TL;DR: Khi một biến chỉ được phép nhận vài giá trị cố định (trạng thái đơn hàng, phân loại BMI), static final int không bảo vệ được gì — mọi int đều lọt qua compiler. enum định nghĩa một kiểu mới mà giá trị hợp lệ là một tập đóng: compiler chặn giá trị lạ, switch expression được kiểm tra exhaustive, và mỗi value có thể mang field + method riêng. Hai quy tắc sống còn: không bao giờ dùng ordinal() cho logic nghiệp vụ (thứ tự khai báo có thể đổi khi refactor), và persist enum bằng name() hoặc field code riêng thay vì số thứ tự.

Bài trước dùng static final cho hằng số đơn lẻ như MAX_RETRIES = 3. Nhưng khi hằng số là một tập giá trị liên quan nhau — trạng thái đơn hàng, mức độ ưu tiên, phân loại BMI — static final int lộ điểm yếu lớn: compiler không ngăn được ai truyền giá trị ngoài tập. Java có hẳn một cơ chế ngôn ngữ cho bài toán này: enum.

1. Analogy menu bảng đèn mô tả kiểu enum như thế nào?

enum như menu in trên bảng đèn trong nhà hàng: tập hợp lựa chọn là cố định, bạn chỉ có thể chọn trong menu đó, không tự thêm món tuỳ tiện — và mỗi món có thể kèm thông tin riêng (giá, độ cay).

Đời thườngConcept
Menu in cố định trên bảng đènTập value của enum — đóng, không thêm lúc runtime
Gọi món ngoài menu bị từ chốiCompiler chặn giá trị ngoài enum
Mỗi món kèm giá + mô tảEnum value có field + method riêng

2. Tại sao Java cần đến enum thay vì hằng số nguyên truyền thống?

Trước khi có enum, người ta dùng int hằng số:

Java
// ANTI-PATTERN: int constants
static final int TRANG_THAI_MOI        = 1;
static final int TRANG_THAI_XU_LY      = 2;
static final int TRANG_THAI_HOAN_TAT   = 3;

void xuLy(int trangThai) {
    if (trangThai == 1) { ... }
    // van co the truyen 999 -- compiler khong phat hien!
}

Vấn đề: không type-safe — bất kỳ int nào đều được chấp nhận, kể cả giá trị không hợp lệ.

3. Cú pháp khai báo và sử dụng enum cơ bản như thế nào?

Java
public enum TrangThai {
    MOI, DANG_XU_LY, HOAN_TAT, HUY_BO;
}

Dùng:

Java
TrangThai t = TrangThai.MOI;  // compiler chi chap nhan gia tri trong enum

void xuLy(TrangThai trangThai) {  // type-safe: chi nhan TrangThai
    if (trangThai == TrangThai.MOI) { ... }
    // khong the truyen int 999 -- compile error
}

4. Cấu trúc switch kết hợp với enum mang lại lợi ích gì?

Java
TrangThai t = TrangThai.DANG_XU_LY;

// Switch expression (Java 14+)
String label = switch (t) {
    case MOI        -> "Moi tao";
    case DANG_XU_LY -> "Dang xu ly";
    case HOAN_TAT   -> "Da xong";
    case HUY_BO     -> "Da huy";
};
// Compiler bao loi neu quen 1 case -- exhaustive check

Thử ngẫmbạn vừa thêm một trạng thái đơn hàng mới vào enum, và một switch expression cũ trong dự án lập tức báo compile error — bạn coi đó là phiền toái, hay đúng thứ bạn muốn xảy ra? Vì sao?

5. Khai báo field và method bên trong enum hoạt động ra sao?

Java
public enum PhanLoaiBmi {
    GAY("Gay", 0, 18.5),
    BINH_THUONG("Binh thuong", 18.5, 25),
    THUA_CAN("Thua can", 25, 30),
    BEO_PHI("Beo phi", 30, Double.MAX_VALUE);

    private final String nhan;
    private final double min;
    private final double max;

    PhanLoaiBmi(String nhan, double min, double max) {
        this.nhan = nhan;
        this.min  = min;
        this.max  = max;
    }

    public String getNhan() { return nhan; }

    // Static factory: tu BMI -> PhanLoaiBmi
    public static PhanLoaiBmi tuBmi(double bmi) {
        for (PhanLoaiBmi pl : values()) {
            if (bmi >= pl.min && bmi < pl.max) return pl;
        }
        return BEO_PHI;
    }
}

// Dung:
double bmi = 22.4;
PhanLoaiBmi pl = PhanLoaiBmi.tuBmi(bmi);
System.out.println(pl.getNhan());  // "Binh thuong"

6. Các phương thức built-in chuẩn của enum gồm những gì?

Java
TrangThai t = TrangThai.DANG_XU_LY;

t.name();      // "DANG_XU_LY" -- ten khai bao
t.ordinal();   // 1 -- vi tri 0-indexed trong enum (MOI=0, DANG_XU_LY=1...)
t.toString();  // "DANG_XU_LY" -- mac dinh giong name()

TrangThai[] all = TrangThai.values();  // mang tat ca gia tri
TrangThai t2 = TrangThai.valueOf("MOI");  // tu String -> enum; IllegalArgumentException neu sai
Pitfall — đừng dùng ordinal() cho logic

ordinal() trả vị trí khai báo trong enum (0, 1, 2...). Nếu ai thêm hoặc đổi thứ tự enum value, ordinal() thay đổi theo — logic dựa vào ordinal() bị vỡ ngầm. Thêm field riêng như code hoặc displayOrder nếu cần số thứ tự ổn định.

Thử ngẫmmột bảng database của bạn lưu trạng thái đơn hàng bằng số lấy từ ordinal() — nếu ai chèn thêm một trạng thái mới vào giữa danh sách, dữ liệu cũ trong bảng còn đúng nghĩa không?

7. Khi nào nên dùng enum thay cho static final int?

enumstatic final int
Type-safe?✅ Compiler kiểm tra❌ Mọi int đều được nhận
Có thể thêm field/method?✅ Dễ dàng❌ Phức tạp
Switch exhaustive check?✅ Compiler cảnh báo case thiếu❌ Không
Serialization/persistenceDùng .name() (String) tốt hơn .ordinal()Stable nếu không đổi value
Dùng khiTập hợp giá trị liên quan, có logicGiá trị đơn lẻ không liên quan nhau

✅ Dùng enum khi: trạng thái (OPEN/CLOSED/PENDING), mức độ (LOW/MEDIUM/HIGH), phân loại (màu sắc, loại sản phẩm), loại sự kiện.

✅ Dùng static final khi: hằng số đơn lẻ không thuộc "họ" nào (MAX_RETRIES = 3, TIMEOUT_MS = 5000).

❌ Dùng magic number: if (status == 2) thay vì if (status == TrangThai.DANG_XU_LY) — khó đọc, không type-safe, dễ sai.

7.1 ✅/❌ Bảng chọn nhanh

Tình huống✅ Dùng❌ Tránh
Tập trạng thái liên quan (OPEN, CLOSED...)enumint magic numbers
Phân loại cố định (loại sản phẩm, màu sắc)enumString hardcoded
Cần switch exhaustiveenumint/String (compiler không check)
Hằng số đơn lẻ như timeout, retry limitstatic final intenum (overkill)
Hằng số string như URL prefixstatic final Stringenum (không thêm value)
Magic number trong if❌ Không dùng gì cả — tạo named constantif (status == 2)
💡 Cách nhớ

enum là "menu cố định" — chỉ có thể chọn trong danh sách, không tự thêm tuỳ tiện, và mỗi lựa chọn có thể mang thêm thông tin. Còn static final là "bảng giá niêm yết" cho từng con số đơn lẻ.

Thử ngẫmdự án bạn có 4 loại phí vận chuyển, mỗi loại chỉ khác nhau ở một con số cố định, không có behavior riêng — bạn chọn enum có field hay static final cho từng biến? Vì sao?

8. Trình biên dịch Java sinh ra cấu trúc gì đằng sau một enum?

Khai báo enum được compiler dịch thành một class trong đó mỗi hằng là đúng một instance tạo sẵn lúc nạp class, từ đó sinh ra ba nhóm API: values trả mảng đủ mọi hằng, name và ordinal trả tên khai báo và vị trí, valueOf phân tích ngược từ String

9. Mã nguồn ví dụ minh họa enum với thuộc tính và phương thức ra sao?

Java
public class OrderDemo {

    public enum TrangThai {
        PENDING("Cho xu ly"),
        PROCESSING("Dang xu ly"),
        SHIPPED("Da giao hang"),
        DELIVERED("Da nhan hang"),
        CANCELLED("Da huy");

        private final String moTa;

        TrangThai(String moTa) {
            this.moTa = moTa;
        }

        public String getMoTa() { return moTa; }

        public boolean canCancel() {
            return this == PENDING || this == PROCESSING;
        }
    }

    public static void main(String[] args) {
        TrangThai t = TrangThai.PROCESSING;

        System.out.println("Trang thai: " + t.getMoTa());
        System.out.println("Co the huy: " + t.canCancel());

        // switch expression -- exhaustive
        String icon = switch (t) {
            case PENDING    -> "CLOCK";
            case PROCESSING -> "GEAR";
            case SHIPPED    -> "TRUCK";
            case DELIVERED  -> "CHECK";
            case CANCELLED  -> "X";
        };
        System.out.println("Icon: " + icon);

        // Built-in methods
        System.out.println(t.name());                      // PROCESSING
        System.out.println(TrangThai.values().length);     // 5
        System.out.println(TrangThai.valueOf("SHIPPED"));  // SHIPPED
    }
}

10. Các bẫy pitfall thường gặp nhất khi làm việc với enum là gì?

❌ Dùng ordinal() cho logic: thứ tự khai báo enum có thể thay đổi khi refactor, ordinal() sẽ trả giá trị khác mà không báo lỗi. Persist/serialize bằng name() hoặc field code riêng.

❌ Magic number: if (status == 2) thay vì if (status == TrangThai.PROCESSING) — khó đọc, không type-safe.

❌ valueOf() với input chưa normalize: TrangThai.valueOf("moi") throw IllegalArgumentException vì match case-sensitive với name(). Input từ user phải normalize (toUpperCase()) + bọc try-catch.

❌ Thêm default vào switch expression đã đủ case: value mới thêm vào enum sẽ rơi im lặng vào default thay vì gây compile error nhắc bạn cập nhật — mất lợi ích exhaustive check.

11. Tài liệu Deep Dive Oracle quy định gì về kiểu enum?

📚 Deep Dive Oracle (optional)

Spec và API chính thức Java 21:

Diễn giải đơn giản: JLS §8.9 quy định enum values là public static final instance của enum class, được khởi tạo theo thứ tự khai báo khi class load — mỗi value đúng một instance duy nhất, đó là lý do so sánh enum bằng == an toàn. ordinal() là thứ tự khai báo (0-indexed) — spec không đảm bảo giá trị ổn định qua refactor, nên đừng persist nó.

12. Điểm cốt lõi cần nhớ về enum trong Java là gì?

  • Type safety: Enum định nghĩa tập giá trị đóng; compiler chặn tuyệt đối các giá trị không hợp lệ.
  • Singleton instance: Mỗi giá trị enum là một thể hiện duy nhất được tạo khi nạp class, cho phép so sánh an toàn bằng ==.
  • Exhaustive switch: Switch expression với enum kiểm tra đủ trường hợp tại compile-time mà không cần nhánh default.
  • Tránh ordinal(): Không dùng .ordinal() cho logic hay lưu DB; dùng .name() hoặc thuộc tính mã định danh riêng.

13. Tự kiểm tra kiến thức về kiểu dữ liệu enum như thế nào?

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Enum TrangThai có values MOI, XU_LY, XONG. TrangThai.MOI.ordinal() trả gì? Nếu thêm DANG_CHO vào đầu danh sách, ordinal của MOI đổi không?
  2. Q2
    Vì sao nên dùng enum thay static final int STATUS_ACTIVE = 1; static final int STATUS_INACTIVE = 2;?
  3. Q3
    Vì sao Java không cho phép new TrangThai(...) từ bên ngoài — enum constructor luôn private? Nếu cho phép thì điều gì vỡ?
  4. Q4
    TrangThai.valueOf("moi") trả gì khi enum có value MOI? Xử lý input từ user thế nào cho an toàn?
  5. Q5
    Vì sao switch expression với enum không nên thêm nhánh default khi đã liệt kê đủ mọi case?

Bài tiếp theo: Mini-challenge: Máy tính BMI

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.

Lộ trình Junior Java Backend Developer

Chặng 2/21 · Lập trình từ số 0 với Java — bài 18/32 của chặng

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Thực hành tính chỉ số BMI và phân loại theo chuẩn WHO