EnumSet/EnumMap

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Trong Java có các bộ sưu tập chuyên biệt để làm việc với liệt kê (enum): EnumSetEnumMap. Chúng thuộc thư viện chuẩn (java.util) và được thiết kế để làm việc hiệu quả tối đa với các kiểu enum.

Bên trong EnumSet và EnumMap hoạt động như thế nào?

EnumSet hoạt động như một mặt nạ bit. Hãy hình dung một tập các cờ, nơi mỗi phần tử của enum chiếm đúng một bit. Nếu bit bật — phần tử có trong tập; nếu tắt — thì không. Tất cả được lưu trong một mảng số (long[]), và nếu enum của bạn có ít hơn 64 giá trị thì toàn bộ tập hợp nằm gọn trong chỉ một số duy nhất!

EnumMap còn đơn giản hơn: đó là một mảng giá trị, trong đó chỉ số là số thứ tự (ordinal) của phần tử enum. Thay vì HashMap<Enum, V> quen thuộc, bạn có được một cấu trúc rất gọn nhẹ và nhanh.

Điều này mang lại gì? Các thao tác thêm, xóa và kiểm tra đều có độ phức tạp O(1). Sử dụng tối thiểu bộ nhớ (đặc biệt khi so với HashSetHashMap). Duyệt phần tử luôn theo thứ tự khai báo trong enum, giúp kết quả dễ dự đoán.

Ví dụ: trông như thế nào

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]

Bên ngoài, đây là một Set thông thường. Nhưng bên trong — một số với hai bit được bật: một cho SAT, một cho SUN. Thêm FRI — sẽ bật thêm một bit. Không có bảng băm và đối tượng dư thừa.

EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
schedule.put(Day.MON, "Gym");
schedule.put(Day.FRI, "Party");
System.out.println(schedule); // {MON=Gym, FRI=Party}

Ở đây các khóa (Day) được chuyển thành các chỉ số nội bộ của mảng, vì vậy truy cập nhanh như truy cập phần tử mảng.

2. Trường hợp sử dụng: cờ, bảng ánh xạ, máy trạng thái hữu hạn

EnumSet: lý tưởng cho cờ và tập trạng thái

  • Cờ: lưu tập tùy chọn bật/tắt cho một số lượng tùy chọn hữu hạn.
  • Tập các giá trị enum: ngày trong tuần, quyền của người dùng, trạng thái tác vụ.
  • Máy trạng thái hữu hạn (FSM): thuận tiện để lưu các chuyển trạng thái hợp lệ trong EnumSet.

Ví dụ: cờ truy cập

enum Permission { READ, WRITE, EXECUTE }

EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
    // Được phép ghi
}

Ví dụ: tất cả giá trị trừ một vài cái

EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]

EnumMap: lý tưởng cho các bảng theo khóa là enum

  • Bảng ánh xạ: khóa là các giá trị enum, giá trị — bất kỳ đối tượng nào.
  • Truy cập nhanh: nhanh và gọn hơn HashMap<Enum, V>.

Ví dụ: giá theo ngày trong tuần

EnumMap<Day, Integer> prices = new EnumMap<>(Day.class);
prices.put(Day.MON, 100);
prices.put(Day.SAT, 200);
System.out.println(prices.get(Day.SAT)); // 200

Ví dụ: máy trạng thái hữu hạn

enum State { START, RUNNING, STOPPED }
EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.START, EnumSet.of(State.RUNNING));
transitions.put(State.RUNNING, EnumSet.of(State.STOPPED));
transitions.put(State.STOPPED, EnumSet.noneOf(State.class));

3. Cạm bẫy: thay đổi enum và tuần tự hóa

EnumSetEnumMap phụ thuộc vào tập và thứ tự các giá trị của enum của bạn. Nếu bạn thêm giá trị mới, xóa giá trị cũ hoặc hoán đổi vị trí của chúng, các bộ sưu tập đã lưu hoặc đã được tuần tự hóa có thể hoạt động không chính xác.

Tuần tự hóa

  • EnumSetEnumMap có thể tuần tự hóa, nhưng khi enum thay đổi giữa lúc tuần tự hóa và giải tuần tự hóa, có thể xảy ra lỗi/mất dữ liệu.
  • Xóa một giá trị khỏi enum sau khi tuần tự hóa gần như chắc chắn sẽ dẫn đến ngoại lệ khi đọc.

Thực hành tốt:

  • Đừng tuần tự hóa EnumSet/EnumMap nếu bạn không chắc enum ổn định.
  • Để lưu trữ lâu dài, hãy dùng ví dụ như danh sách các biểu diễn chuỗi của giá trị.

4. Các điểm hữu ích

So sánh EnumSet/EnumMap với các bộ sưu tập thông thường

Bộ sưu tập Khóa/phần tử Cấu trúc bên trong Hiệu năng Bộ nhớ Null
EnumSet
enum
mặt nạ bit O(1) rất ít không cho phép
HashSet<Enum>
enum
bảng băm O(1) nhiều hơn có thể
EnumMap
enum
mảng dựa trên ordinal O(1) rất ít không cho phép
HashMap<Enum, V>
enum
bảng băm O(1) nhiều hơn có thể

Best practices

  • Sử dụng EnumSet cho các tập giá trị (of, noneOf, allOf, complementOf).
  • Sử dụng EnumMap cho các bảng ánh xạ nơi khóa là enum.
  • Tránh các liệt kê “khổng lồ” — danh sách hàng trăm giá trị làm giảm tính gọn nhẹ.
  • Đừng tuần tự hóa nếu enum có thể thay đổi.
  • Không dùng null làm khóa hoặc giá trị.

5. Thực hành: cách sử dụng EnumSet và EnumMap trong ứng dụng

Ví dụ: lưu trữ vai trò người dùng

enum Role { USER, ADMIN, MODERATOR }

class User {
    private EnumSet<Role> roles = EnumSet.noneOf(Role.class);

    public void addRole(Role role) {
        roles.add(role);
    }

    public boolean isAdmin() {
        return roles.contains(Role.ADMIN);
    }
}

Ví dụ: bảng chuyển trạng thái

enum State { NEW, IN_PROGRESS, DONE }

EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.NEW, EnumSet.of(State.IN_PROGRESS));
transitions.put(State.IN_PROGRESS, EnumSet.of(State.DONE));
transitions.put(State.DONE, EnumSet.noneOf(State.class));

6. Lỗi thường gặp khi làm việc với EnumSet/EnumMap

Lỗi số 1: Dùng EnumSet/EnumMap với kiểu không phải enum.
Các bộ sưu tập này chỉ hoạt động với các kiểu là enum.

// EnumSet<String> set = EnumSet.of("A", "B"); // Lỗi biên dịch!

Lỗi số 2: Dùng EnumSet/EnumMap cho enum rất lớn.
Các enum có hàng trăm giá trị làm giảm tính gọn nhẹ. Tuy vậy, điều này thường vẫn tốt hơn về bộ nhớ so với HashSet/HashMap cho cùng lượng dữ liệu.

Lỗi số 3: Thay đổi enum sau khi tuần tự hóa.
Thêm/xóa/đổi thứ tự các giá trị sau khi tuần tự hóa sẽ dẫn tới lỗi khi đọc hoặc mất dữ liệu.

Lỗi số 4: Dùng EnumSet/EnumMap với null.
Cả phần tử của EnumSet lẫn khóa/giá trị của EnumMap đều không thể là null.

EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!

Lỗi số 5: Kỳ vọng rằng EnumSet là một Set “thông thường”.
EnumSet chỉ lưu các giá trị của enum của nó; bạn không thể thêm đối tượng tùy ý nằm ngoài liệt kê.

Lỗi số 6: Dùng EnumSet/EnumMap cho enum “có thể thay đổi”.
Nếu các enum được sinh ra hoặc bị thay thế ở thời gian chạy (tải động/phản chiếu), các cấu trúc này sẽ không hoạt động đúng.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Lập kế hoạch cuối tuần hoàn hảo 🏖️
Lập kế hoạch cuối tuần hoàn hảo 🏖️
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Định giá động tại phòng trưng bày nghệ thuật "Cảm hứng" 🎨
Định giá động tại phòng trưng bày nghệ thuật "Cảm hứng" 🎨
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION