Backend

99 Ngày Java — Ngày 23: Enum

SSite Admin
19 tháng 08, 2026 6 phút đọc 0 lượt xem
99 Ngày Java — Ngày 23: Enum

Ngày 22 khép lại với lời hẹn về kiểu dữ liệu mô hình hóa tập giá trị cố định — hôm nay ta gặp enum. Trạng thái đơn hàng, phương thức thanh toán, ngày trong tuần: những giá trị đếm được trên đầu ngón tay xứng đáng có kiểu riêng thay vì hằng số int hay String rời rạc. Và enum trong Java không dừng ở danh sách tên: nó là lớp đầy đủ với field, constructor, method — kèm hai vũ khí ít người tận dụng là switch exhaustive và bộ đôi EnumMap/EnumSet.

Sketchnote Ngày 23: enum — tập giá trị cố định, field và method, switch exhaustive, EnumMap và EnumSet, lưu trữ theo name

Enum cơ bản — tập giá trị cố định, an toàn kiểu

// Enum — kiểu liệt kê: tập giá trị CỐ ĐỊNH, biết trước khi biên dịch
public enum TrangThaiDonHang {
    MOI, DA_XAC_NHAN, DANG_GIAO, HOAN_TAT, DA_HUY
}

TrangThaiDonHang tt = TrangThaiDonHang.DANG_GIAO;

// Mỗi hằng số là MỘT đối tượng duy nhất do JVM tạo sẵn —
// nên == an toàn tuyệt đối (trái ngược wrapper của Ngày 22!):
tt == TrangThaiDonHang.DANG_GIAO;     // ✓ true, luôn luôn

// API có sẵn cho mọi enum:
TrangThaiDonHang.values();            // mảng mọi hằng số, đúng thứ tự khai báo
TrangThaiDonHang.valueOf("MOI");      // chuỗi → hằng số
TrangThaiDonHang.valueOf("XXX");      // ✗ IllegalArgumentException
tt.name();                            // "DANG_GIAO"
tt.ordinal();                         // 2 — vị trí khai báo; KHÔNG đem lưu trữ,
                                      // chèn hằng số mới là mọi ordinal xô lệch
  • So với hằng số int/String: enum an toàn kiểu (không thể gán "DANG_GIAOO" gõ nhầm), tự liệt kê được qua values(), và debug ra tên dễ đọc thay vì con số vô danh.

  • Mỗi hằng số là singleton do JVM bảo đảm — nên == với enum luôn đúng, ngược hẳn bài wrapper hôm qua: cùng một phép so sánh, độ tin cậy phụ thuộc vào kiểu dữ liệu đứng sau.

  • ordinal() là chi tiết cài đặt: chèn một hằng số mới vào giữa là mọi vị trí xô lệch — không bao giờ đem lưu DB hay so sánh nghiệp vụ, hãy dùng name().

Enum là lớp đầy đủ — field, constructor, method

public enum PhuongThucThanhToan {
    // Mỗi hằng số gọi constructor với dữ liệu riêng của nó:
    TIEN_MAT("Tiền mặt", 0),
    THE_TIN_DUNG("Thẻ tín dụng", 2.5),
    VI_DIEN_TU("Ví điện tử", 1.0);

    private final String ten;             // field — final: hằng số nên bất biến
    private final double phiPhanTram;

    // constructor của enum mặc định (và bắt buộc) là private —
    // không ai new thêm hằng số từ bên ngoài
    PhuongThucThanhToan(String ten, double phiPhanTram) {
        this.ten = ten;
        this.phiPhanTram = phiPhanTram;
    }

    public double tinhPhi(double soTien) {    // method như lớp thường
        return soTien * phiPhanTram / 100;
    }
}

PhuongThucThanhToan.THE_TIN_DUNG.tinhPhi(2_000_000);   // 50000.0
// Logic đi theo dữ liệu của từng hằng số — không cần bảng tra cứu ngoài
  • Đây là điểm nâng enum Java lên trên enum của nhiều ngôn ngữ khác: mỗi hằng số mang dữ liệu riêng (tên hiển thị, phí) và hành vi (tinhPhi) — logic đi theo hằng số thay vì rải rác trong các bảng tra cứu if-else.

  • Constructor của enum bắt buộc private — chính ràng buộc này bảo đảm tập giá trị đóng: toàn bộ hằng số nằm gọn trong file khai báo, không nơi nào tạo thêm được.

  • Field nên final: hằng số dùng chung toàn ứng dụng, một chỗ sửa được trạng thái là mọi nơi cùng thấy — bài học bất biến của Ngày 20 áp dụng nguyên vẹn.

switch với enum — exhaustiveness

// Switch expression (Ngày 06) + enum = bộ đôi exhaustive:
String thongBao = switch (trangThai) {
    case MOI              -> "Đơn vừa tạo, chờ xác nhận";
    case DA_XAC_NHAN      -> "Đã xác nhận, đang chuẩn bị hàng";
    case DANG_GIAO        -> "Đang trên đường giao";
    case HOAN_TAT, DA_HUY -> "Đơn đã đóng";
};   // đủ mọi hằng số → KHÔNG cần default

// Sức mạnh thật sự nằm ở lúc THAY ĐỔI:
// thêm hằng số DOI_TRA vào enum → mọi switch expression thiếu case
// FAIL NGAY KHI BIÊN DỊCH — compiler liệt kê giúp mọi chỗ cần cập nhật.

// Vì thế: đừng viết default "cho chắc" —
// default nuốt mất cảnh báo quý giá đó.
  • Switch expression đủ mọi case thì không cần default — và đó là lựa chọn có chủ đích: khi enum mọc thêm hằng số, compiler chỉ đích danh từng switch cần bổ sung, một dạng lưới an toàn miễn phí khi refactor.

  • Viết default "cho chắc" là đánh đổi tai hại: code chạy tiếp bằng nhánh chung chung thay vì báo lỗi biên dịch — lỗi chỉ lộ ra ở runtime, đúng loại lỗi khó tìm nhất.

  • Kỹ thuật này áp dụng cho cả pattern matching với sealed classes (Ngày 83) — exhaustiveness là chủ đề xuyên suốt Java hiện đại.

Đèn giao thông ba màu trên nền trời — enum là tập trạng thái cố định: mỗi màu một hằng số, không có giá trị thứ tư

EnumMap & EnumSet — collection chuyên dụng

// EnumMap — Map với key là enum: mảng theo ordinal bên trong, gọn và nhanh
Map<TrangThaiDonHang, Long> demTheoTrangThai =
        new EnumMap<>(TrangThaiDonHang.class);
demTheoTrangThai.put(TrangThaiDonHang.MOI, 12L);

// EnumSet — Set các hằng số enum: bit vector bên trong, một long 64 bit
EnumSet<TrangThaiDonHang> dangMo =
        EnumSet.of(TrangThaiDonHang.MOI, TrangThaiDonHang.DA_XAC_NHAN);
EnumSet<TrangThaiDonHang> daDong = EnumSet.complementOf(dangMo);

dangMo.contains(tt);       // O(1) — một phép AND bit

// Lưu enum ra ngoài (DB, JSON): LUÔN theo name(), không theo ordinal —
// với JPA là @Enumerated(EnumType.STRING) (series Spring, Ngày 39)
  • EnumMap lưu value trong mảng đánh chỉ số theo ordinal — không hash, không collision, duyệt đúng thứ tự khai báo; đếm số lượng theo trạng thái là use case điển hình.

  • EnumSet nén cả tập hợp vào một bit vector: phép contains/complementOf chỉ là thao tác bit — thay thế hoàn hảo cho những chuỗi cờ boolean rời rạc.

  • Quy tắc lưu trữ chốt bài: enum ra ngoài hệ thống luôn theo name() — với JPA là @Enumerated(EnumType.STRING), chi tiết chờ ở series Spring Ngày 39; bài Spring hôm nay cũng chạm mặt enum trong derived query.

Bài tập nhỏ

  • Viết enum NgayTrongTuan 7 hằng số kèm field laCuoiTuan (boolean) — thêm method static đếm số ngày làm việc bằng values().

  • Mở rộng PhuongThucThanhToan: thêm hằng số CHUYEN_KHOAN phí 0.5% — quan sát mọi switch expression thiếu case báo lỗi biên dịch ở đâu.

  • Dùng EnumMap đếm số đơn theo TrangThaiDonHang từ một mảng trạng thái cho trước — in kết quả đúng thứ tự khai báo.

  • Thử valueOf với chuỗi sai tên và bắt IllegalArgumentException — viết method timAnToan trả về Optional thay vì ném lỗi.

Kết luận

Enum gói trong bốn ý: tập giá trị cố định và an toàn kiểu với == đáng tin (điều wrapper không có), lớp đầy đủ để logic đi theo từng hằng số, switch exhaustive biến compiler thành lưới an toàn khi thay đổi, và EnumMap/EnumSet cho hiệu năng chuyên dụng. Ngày 24 ta xuống nền móng của mọi đối tượng: equals, hashCode và toString — bộ ba contract của Object mà HashMap đặt trọn niềm tin. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 23: Spring Data JPA repository

Ngày 23 của 99 Ngày Spring: Spring Data JPA repository — JpaRepository với bộ CRUD có sẵn trên một interface trống, phả hệ Repository → CrudRepository → JpaRepository và cơ chế proxy trên SimpleJpaRepository, derived query sinh truy vấn từ tên method với lưới fail-fast lúc khởi động, cùng ranh giới thực dụng để chuyển sang @Query.

19 thg 8, 20266 phút1
99 Ngày Java — Ngày 22: Wrapper class & autoboxing

Ngày 22 của 99 Ngày Java: wrapper class và autoboxing — cầu nối tự động giữa primitive và đối tượng, Integer cache với hiện tượng == đúng ở 127 sai ở 128, bẫy unboxing null gây NPE, boxing thầm lặng trong vòng lặp, và cách chọn giữa parseInt với valueOf.

18 thg 8, 20266 phút26
99 Ngày Spring — Ngày 22: Entity mapping cơ bản

Ngày 22 của 99 Ngày Spring: entity đầu tiên với @Entity/@Id và lý do bắt buộc no-arg constructor (entity là class, không phải record), chiến lược sinh khóa IDENTITY vs SEQUENCE trên PostgreSQL, @Column với ràng buộc và ánh xạ kiểu — wrapper cho cột nullable, cùng show-sql và quy ước snake_case.

18 thg 8, 20266 phút20