Java 集合:EnumMap 类

EnumMap 类也是 Java 集合框架的成员之一。该类是 Java 集合框架专门为 Map 中键为枚举类型而专门设计实现,它继承自 AbstractMap 并实现了 Map 接口。与普通 Map 相比,EnumMap 利用枚举类型的特性提供了更高的性能和更简洁的实现

注意,EnumMap 中的所有键都必须来自创建映射时显式或隐式指定的单一枚举类型。EnumMap 在内部表示为数组,这种表示方式极其紧凑且高效。

EnumMap 按照其键的自然顺序(枚举常量声明的顺序)进行维护,这一点体现在集合视图(keySet()、entrySet() 和 values())返回的迭代器中。

注意,keySet ()、entrySet ()、values () 方法返回的集合视图的迭代器具有弱一致性:它们永远不会抛出ConcurrentModificationException 异常,并且可能会(也可能不会)反映迭代过程中对映射所做的任何修改的结果。

EnumMap 不允许键为 null(但是,值允许为 null)。尝试插入 null 键会抛出 NullPointerException 异常。不过,尝试检测 null  键是否存在或移除 null  键的操作可以正常执行。

与大多数集合实现一样,EnumMap 是不同步的。如果多个线程并发访问一个枚举映射,且至少有一个线程修改该映射,则应在外部进行同步。如使用 Collections.synchronizedMap(java.util.Map<K, V>) 方法来“包装”该映射。最好在创建时就这样做,以防止意外的非同步访问:

Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));

注意,EnumMap 的所有基本操作都以常量时间执行。它们很可能比对应的 HashMap 操作更快。

  

EnumMap 重要特性

下面是 EnumMap 的几个重要特性:

(1) 键类型限制:键必须是某个枚举类型的实例,在创建 EnumMap 时必须显式指定枚举类。例如:

EnumMap<Season, String> map = new EnumMap<>(Season.class);

其中,Season 是枚举类。

(2) 存储结构:内部使用数组存储,数组长度等于枚举类的常量个数。利用枚举常量的 ordinal () 值作为数组索引,因此查找、插入、删除操作效率极高(时间复杂度为 O (1))。源码:

/**
 * @param <K> 键的类型,必须是枚举类型的子类
 * @param <V> 值的类型
 */
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable {
    /**
     * 当前映射中所有键所属的枚举类型的Class对象。
     * 用于确保所有键都属于同一枚举类型,是构造EnumMap的核心依据。
     */
    private final Class<K> keyType;

    /**
     * 存储当前枚举类型K的所有枚举常量数组。
     * 缓存该数组以提升性能,避免频繁调用 Enum.values() 方法。
     */
    private transient K[] keyUniverse;

    /**
     * 该数组是 EnumMap 的核心存储结构。
     * 数组索引与 keyUniverse 中枚举常量的顺序一一对应:
     * (1)第i个元素表示 keyUniverse[i] 对应的 value 值
     * (2)若为 null,可能表示该枚举键未映射任何值,或映射值为 null(通过内部标记区分)
     */
    private transient Object[] vals;

    /**
     * 当前映射中包含的键值对数量。
     * 用于快速获取映射大小,避免每次都遍历vals数组计数。
     */
    private transient int size = 0;

}

这就是 EnumMap 如此高效的原因,这就相当于通过下标操作数组。

(3)有序性:迭代时按照枚举常量在枚举类中的声明顺序排列,无需额外排序操作。源码:

// EnumMap的抽象迭代器实现,用于遍历枚举映射中的元素(键、值或键值对)
private abstract class EnumMapIterator<T> implements Iterator<T> {
    /**
     * 下一个待返回元素的索引下界(起始查找位置)。
     * 用于遍历 vals 数组时记录当前遍历位置,避免重复检查已处理的索引。
     */
    int index = 0;

    /**
     * 最后一个返回元素的索引,若未返回过元素则为-1。
     * 用于支持 remove() 方法,确保只能删除最近一次返回的元素。
     */
    int lastReturnedIndex = -1;

    /**
     * 判断迭代器是否还有下一个元素。
     * 从当前 index 开始遍历 vals 数组,跳过值为 null 的位置(表示无映射),
     * 直到找到有映射的元素或遍历完数组。
     *
     * @return 若存在下一个元素则返回true,否则返回false
     */
    public boolean hasNext() {
        // 跳过数组中值为null的位置(无映射),定位到下一个有效元素
        while (index < vals.length && vals[index] == null)
            index++;
        // 若index未超出数组长度,说明存在下一个元素
        return index != vals.length;
    }

    //...
}

通过上述源码,hasNext() 方法将从下标为 0 开始遍历数组内容,而 EnumMap 又是通过枚举中常量声明顺序存放的,因此最终迭代的顺序就是枚举声明的顺序。

(4)空值支持:允许值为 null,但键不能为 null。如果键为 null,则抛出 NullPointerException 异常。

(5)非线程安全:EnumMap 本身不是线程安全的,多线程环境下需手动同步,如使用 Collections.synchronizedMap() 方法进行包装。

  

EnumMap 构造方法

EnumMap 提供了多种构造方法:

  • EnumMap(Class<K> keyType)  创建一个指定枚举类型的空 EnumMap。例如:

EnumMap<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
  • EnumMap(EnumMap<K, ? extends V> m)  复制另一个 EnumMap 的内容(同类型枚举键)。

  • EnumMap(Map<K, ? extends V> m)  从普通 Map 复制内容,要求该 Map 的键必须是同一枚举类型。

EnumMap 常用方法

EnumMap 实现了 Map 接口的所有方法,核心方法包括:

V put(K key, V value)  

向映射中添加键值对。若该键已存在,则覆盖旧值并返回旧值;若不存在,则新增映射并返回 null。注意:

  • 键(key)必须是当前 EnumMap 声明的枚举类型实例,否则抛出 ClassCastException;不允许为 null,否则抛出 NullPointerException。

  • 内部通过枚举常量的 ordinal() 方法获取索引(对应 keyUniverse 数组的位置),直接操作 vals 数组完成赋值,时间复杂度为 O(1)。

  • 若添加的是新键(原位置为 null),会使 size 递增。

void putAll(Map<? extends K, ? extends V> m)

批量添加另一个映射(m)中的所有键值对到当前 EnumMap 中。注意:

  • 若 m 是另一个 EnumMap,会直接利用其内部数组高效拷贝,避免重复校验;若为普通 Map,则遍历其键值对逐个调用 put 方法。

  • 若 m 中包含当前 EnumMap 不支持的键类型(非目标枚举类型),会在遍历过程中抛出 ClassCastException。

V get(Object key)

根据键获取对应的值。若键不存在或值为 null,均返回 null(需通过 containsKey 区分 “键不存在” 和 “值为 null”)。注意:键(key)需是当前枚举类型实例(否则返回 null,不抛异常),通过 ordinal() 索引直接访问 vals 数组,时间复杂度 O(1)。

Set<K> keySet()

返回包含所有键的 Set 视图,集合元素按枚举常量的自然顺序(声明顺序)排列。注意:

  • 返回的 Set 是 “视图” 而非副本,修改 EnumMap 会同步反映到该集合中(如新增键会自动加入集合)。

  • 迭代该集合时,使用 EnumMap 内部的弱一致性迭代器,不会抛出 ConcurrentModificationException。

Collection<V> values()

返回包含所有值的 Collection 视图,值的顺序与对应键的枚举自然顺序一致。注意:

  • 与 keySet 类似,是 “视图” 而非副本,支持通过迭代器修改值(但不支持添加 / 删除元素)。

  • 若多个键对应的值为 null,集合中会包含多个 null(与 put 时允许 null 值的特性一致)。

Set<Map.Entry<K, V>> entrySet()

返回包含所有键值对(Map.Entry)的 Set 视图,键值对按键的枚举自然顺序排列。注意:

  • 每个 Entry 实例对应 keyUniverse 和 vals 数组的同一索引,迭代时按顺序遍历有效映射(跳过 vals 中为 null 的位置)。

  • 支持通过 Entry.setValue() 修改值,会同步更新 EnumMap 内部的 vals 数组。

V remove(Object key)

删除指定键对应的键值对,返回被删除的值(若键不存在则返回 null)。注意:

  • 键(key)需是当前枚举类型实例(否则返回 null),通过 ordinal() 索引定位后将 vals 数组对应位置设为 null,并使 size 递减(仅当原位置有值时)。

  • 时间复杂度 O(1),比 HashMap 的删除更高效(无需处理哈希冲突)。

void clear()

清空映射中所有键值对。注意:内部通过遍历 vals 数组,将所有非 null 位置设为 null,并重置 size 为 0,时间复杂度 O(n)(n 为枚举类型的常量总数)。

boolean containsKey(Object key)

判断映射中是否包含指定键。注意:键(key)需是当前枚举类型实例(否则返回 false),通过 ordinal() 索引检查 vals 数组对应位置是否为 null(不为 null 则存在),时间复杂度 O(1)。

boolean containsValue(Object value)

判断映射中是否包含指定值。注意:需遍历 vals 数组检查是否有匹配的值(null 值通过 == 判断,非 null 值通过 equals 比较),时间复杂度 O(n)(n 为枚举类型的常量总数)。

int size()

返回映射中键值对的数量(非枚举类型的总常量数)。注意:直接返回内部维护的 size 变量,时间复杂度 O(1)(无需遍历数组计数)。

boolean isEmpty()

判断映射是否为空(无任何键值对)。注意:通过判断 size == 0 实现,时间复杂度 O(1)。

  

简单示例

下面通过一个简单的示例来介绍 EnumMap 的基本用法:

package com.hxstrive.java_collection.enumMap;

import java.util.EnumMap;
import java.util.Map;

// 定义枚举类
enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}

public class EnumMapExample {
    public static void main(String[] args) {
        // 创建EnumMap
        EnumMap<Season, String> seasonDesc = new EnumMap<>(Season.class);
        
        // 添加元素
        seasonDesc.put(Season.SPRING, "春暖花开");
        seasonDesc.put(Season.SUMMER, "夏日炎炎");
        seasonDesc.put(Season.AUTUMN, "秋高气爽");
        seasonDesc.put(Season.WINTER, "寒冬腊月");
        
        // 获取元素
        System.out.println("春天的描述:" + seasonDesc.get(Season.SPRING));
        
        // 遍历(按枚举声明顺序)
        System.out.println("\n所有季节:");
        for (Map.Entry<Season, String> entry : seasonDesc.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // 检查是否包含键
        System.out.println("\n是否包含冬季?" + seasonDesc.containsKey(Season.WINTER));
        
        // 删除元素
        seasonDesc.remove(Season.SUMMER);
        System.out.println("\n删除夏季后大小:" + seasonDesc.size());
    }
}

输出结果:

春天的描述:春暖花开

所有季节:
SPRING: 春暖花开
SUMMER: 夏日炎炎
AUTUMN: 秋高气爽
WINTER: 寒冬腊月

是否包含冬季?true

删除夏季后大小:3

  

与其他 Map 的对比

特性

EnumMap

HashMap

TreeMap

键类型

仅限枚举类型

任意对象

任意对象(需可比较)

性能

极高(数组索引访问)

高(哈希表)

中(红黑树)

有序性

枚举声明顺序

无序

自然顺序或比较器顺序

内存占用

紧凑(固定数组长度)

较高(哈希表开销)

较高(树结构开销)

空键支持

不支持

支持(仅一个 null 键)

不支持(自然排序时)

更多信息参考 https://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html API 文档。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号