1. 概述

EnumMap 是 Java 中 Map 接口的一个实现类,它只接受枚举类型(enum)作为键

本文将带你深入了解 EnumMap 的特性、使用场景以及为什么在某些情况下它是更好的选择。

2. 场景准备

假设我们有一个简单的需求:将一周中的每一天映射到当天进行的运动项目上:

Monday     Soccer                         
Tuesday    Basketball                     
Wednesday  Hiking                         
Thursday   Karate

为此我们可以定义一个枚举:

public enum DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

这个枚举将成为我们后续 EnumMap 的 key 类型。

3. 创建 EnumMap

要开始使用 EnumMap,首先需要实例化它:

EnumMap<DayOfWeek, String> activityMap = new EnumMap<>(DayOfWeek.class);
activityMap.put(DayOfWeek.MONDAY, "Soccer");

这里和常见的 HashMap 有个关键区别:**EnumMap 构造时必须传入枚举类型的 class 对象**。而 HashMap 只需写成 new HashMap<>() 即可。

3.1. EnumMap 的拷贝构造器

EnumMap 提供了两个拷贝构造函数。

第一个是传入另一个 EnumMap

EnumMap<DayOfWeek, String> activityMap = new EnumMap<>(DayOfWeek.class);
activityMap.put(DayOfWeek.MONDAY, "Soccer");
activityMap.put(DayOfWeek.TUESDAY, "Basketball");

EnumMap<DayOfWeek, String> activityMapCopy = new EnumMap<>(activityMap);
assertThat(activityMapCopy.size()).isEqualTo(2);
assertThat(activityMapCopy.get(DayOfWeek.MONDAY)).isEqualTo("Soccer");
assertThat(activityMapCopy.get(DayOfWeek.TUESDAY)).isEqualTo("Basketball");

3.2. Map 的拷贝构造器

或者,如果你有一个非空的普通 Map,且其 key 是枚举类型,也可以直接构造:

Map<DayOfWeek, String> ordinaryMap = new HashMap<>();
ordinaryMap.put(DayOfWeek.MONDAY, "Soccer");

EnumMap<DayOfWeek, String> enumMap = new EnumMap<>(ordinaryMap);
assertThat(enumMap.size()).isEqualTo(1);
assertThat(enumMap.get(DayOfWeek.MONDAY)).isEqualTo("Soccer");

⚠️ 注意:传入的 Map 必须是非空的,否则 EnumMap 就无法推断 key 类型。

如果 Map 中包含多个不同枚举类型,则会抛出 ClassCastException

4. 添加与获取元素

创建好 EnumMap 后,我们可以通过 put() 方法添加键值对:

activityMap.put(DayOfWeek.MONDAY, "Soccer");

获取值则使用 get() 方法:

assertThat(activityMap.get(DayOfWeek.MONDAY)).isEqualTo("Soccer");

5. 判断元素是否存在

要判断某个枚举值是否已被映射,使用 containsKey()

activityMap.put(DayOfWeek.WEDNESDAY, "Hiking");
assertThat(activityMap.containsKey(DayOfWeek.WEDNESDAY)).isTrue();

要判断某个值是否被映射过,使用 containsValue()

assertThat(activityMap.containsValue("Hiking")).isTrue();

5.1. 支持 null 值

EnumMap 允许 value 为 null,语义上可以表示“无活动”。

assertThat(activityMap.containsKey(DayOfWeek.SATURDAY)).isFalse();
assertThat(activityMap.containsValue(null)).isFalse();

activityMap.put(DayOfWeek.SATURDAY, null);

assertThat(activityMap.containsKey(DayOfWeek.SATURDAY)).isTrue();
assertThat(activityMap.containsValue(null)).isTrue();

6. 删除元素

删除某个键对应的映射关系,使用 remove(key)

activityMap.put(DayOfWeek.MONDAY, "Soccer");
assertThat(activityMap.remove(DayOfWeek.MONDAY)).isEqualTo("Soccer");
assertThat(activityMap.containsKey(DayOfWeek.MONDAY)).isFalse();

返回值是被删除的 value,如果没有映射则返回 null

也可以选择性删除:只有当 key 映射到指定值时才删除

activityMap.put(DayOfWeek.MONDAY, "Soccer");
assertThat(activityMap.remove(DayOfWeek.MONDAY, "Hiking")).isFalse();
assertThat(activityMap.remove(DayOfWeek.MONDAY, "Soccer")).isTrue();

remove(key, value) 方法只有在 key 当前映射到 value 时才会删除。

7. 集合视图

和普通 Map 一样,EnumMap 也提供三种集合视图:

先创建一个活动映射:

EnumMap<DayOfWeek, String> activityMap = new EnumMap<>(DayOfWeek.class);
activityMap.put(DayOfWeek.THURSDAY, "Karate");
activityMap.put(DayOfWeek.WEDNESDAY, "Hiking");
activityMap.put(DayOfWeek.MONDAY, "Soccer");

7.1. values()

返回所有 value 的集合:

Collection<String> values = activityMap.values();
assertThat(values).containsExactly("Soccer", "Hiking", "Karate");

✅ 注意:EnumMap 是有序的,其顺序与枚举常量定义顺序一致。

7.2. keySet()

返回所有 key 的集合,同样保持枚举顺序:

Set<DayOfWeek> keys = activityMap.keySet();
assertThat(keys)
    .containsExactly(DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY);

7.3. entrySet()

返回键值对集合:

assertThat(activityMap.entrySet())
    .containsExactly(
        new SimpleEntry<>(DayOfWeek.MONDAY, "Soccer"),
        new SimpleEntry<>(DayOfWeek.WEDNESDAY, "Hiking"),
        new SimpleEntry<>(DayOfWeek.THURSDAY, "Karate")
    );

7.4. 视图的可变性

对原 map 的修改会反映在视图中:

activityMap.put(DayOfWeek.TUESDAY, "Basketball");
assertThat(values).containsExactly("Soccer", "Basketball", "Hiking", "Karate");

反之,对视图的修改也会反映回原 map:

values.remove("Hiking");
assertThat(activityMap.containsKey(DayOfWeek.WEDNESDAY)).isFalse();
assertThat(activityMap.size()).isEqualTo(3);

✅ 根据 Map 接口规范,这些视图是由原 map 支持的,不是独立副本。

8. 何时使用 EnumMap

8.1. 性能优势

由于 key 是枚举类型,所有可能的 key 都是已知的,因此可以进行优化:

  • hash 计算更高效
  • 内部实现基于数组,逻辑简单
  • 不会出现 hash 冲突,避免了 HashMap 那种复杂的数据结构

8.2. 功能优势

  • ✅ 保持枚举顺序,适合需要排序的场景
  • HashMap 更安全,key 类型严格限定
  • TreeMap 更轻量,不需要比较器

9. 总结

当你需要使用枚举作为 Map 的 key 时,EnumMap 是一个简单粗暴又高效的选择。它不仅性能优越,还提供了良好的语义支持和顺序保障。

所有示例代码可在 GitHub 项目 中找到。


原始标题:A Guide to EnumMap