map;
大部分代码都很简单,需要注意的是EnumMap的构造方法,如下所示:
Map<Size, Integer> map = new EnumMap<>(Size.class);
与HashMap不同,它需要传递一个类型信息,我们在37节简单介绍过运行时类型信息,Size.class表示枚举类Size的运行时类型信息,Size.class也是一个对象,它的类型是Class。
为什么需要这个参数呢?没有这个,EnumMap就不知道具体的枚举类是什么,也无法初始化内部的数据结构。
使用以上的统计方法也是很简单的,比如:
List<Clothes> clothes = Arrays.asList(new Clothes[]{
new Clothes("C001",Size.SMALL),
new Clothes("C002", Size.LARGE),
new Clothes("C003", Size.LARGE),
new Clothes("C004", Size.MEDIUM),
new Clothes("C005", Size.SMALL),
new Clothes("C006", Size.SMALL),
System.out.println(countBySize(clothes));
{SMALL=3, MEDIUM=1, LARGE=2}
需要说明的是,EnumMap是保证顺序的,输出是按照键在枚举中的顺序的。
除了以上介绍的构造方法,EnumMap还有两个构造方法,可以接受一个键值匹配的EnumMap或普通Map,如下所示:
public EnumMap(EnumMap<K, ? extends V> m)
public EnumMap(Map<K, ? extends V> m)
Map<Size,Integer> hashMap = new HashMap<>();
hashMap.put(Size.LARGE, 2);
hashMap.put(Size.SMALL, 1);
Map<Size, Integer> enumMap = new EnumMap<>(hashMap);
以上就是EnumMap的基本用法,与HashMap的主要不同,一是构造方法需要传递类型参数,二是保证顺序。
有人可能认为,对于枚举,使用Map是没有必要的,比如对于上面的统计例子,可以使用一个简单的数组:
public static int[] countBySize(List<Clothes> clothes){
int[] stat = new int[Size.values().length];
for(Clothes c : clothes){
Size size = c.getSize();
stat[size.ordinal()]++;
return stat;
这个方法可以这么使用:
List<Clothes> clothes = Arrays.asList(new Clothes[]{
new Clothes("C001",Size.SMALL),
new Clothes("C002", Size.LARGE),
new Clothes("C003", Size.LARGE),
new Clothes("C004", Size.MEDIUM),
new Clothes("C005", Size.SMALL),
new Clothes("C006", Size.SMALL),
int[] stat = countBySize(clothes);
for(int i=0; i<stat.length; i++){
System.out.println(Size.values()[i]+": "+ stat[i]);
SMALL 3
MEDIUM 1
LARGE 2
可以达到同样的目的。但,直接使用数组需要自己维护数组索引和枚举值之间的关系,正如枚举的优点是简洁、安全、方便一样,EnumMap同样是更为简洁、安全、方便,它内部也是基于数组实现的,但隐藏了细节,提供了更为方便安全的接口。
下面我们来看下具体的代码,从内部组成开始。
EnumMap有如下实例变量:
private final Class<K> keyType;
private transient K[] keyUniverse;
private transient Object[] vals;
private transient int size = 0;
keyType表示类型信息,keyUniverse表示键,是所有可能的枚举值,vals表示键对应的值,size表示键值对个数。
EnumMap的基本构造方法代码为:
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
调用了getKeyUniverse以初始化键数组,其代码为:
private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
return SharedSecrets.getJavaLangAccess()
.getEnumConstantsShared(keyType);
这段代码又调用了其他一些比较底层的代码,就不列举了,原理是最终调用了枚举类型的values方法,values方法返回所有可能的枚举值。关于values方法,我们在枚举的本质一节介绍过其用法和实现原理,这里就不赘述了。
保存键值对
put方法的代码为:
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
首先调用typeCheck检查键的类型,其代码为:
private void typeCheck(K key) {
Class keyClass = key.getClass();
if (keyClass != keyType && keyClass.getSuperclass() != keyType)
throw new ClassCastException(keyClass + " != " + keyType);
如果类型不对,会抛出异常。类型正确的话,调用ordinal获取索引index,并将值value放入值数组vals[index]中。EnumMap允许值为null,为了区别null值与没有值,EnumMap将null值包装成了一个特殊的对象,有两个辅助方法用于null的打包和解包,打包方法为maskNull,解包方法为unmaskNull。这个特殊对象及两个方法的代码为:
private static final Object NULL = new Object() {
public int hashCode() {
return 0;
public String toString() {
return "java.util.EnumMap.NULL";
private Object maskNull(Object value) {
return (value == null ? NULL : value);
private V unmaskNull(Object value) {
return (V) (value == NULL ? null : value);
根据键获取值
get方法的代码为:
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum)key).ordinal()]) : null);
键有效的话,通过ordinal方法取索引,然后直接在值数组vals里找。isValidKey的代码与typeCheck类似,但是返回boolean值而不是抛出异常,代码为:
private boolean isValidKey(Object key) {
if (key == null)
return false;
// Cheaper than instanceof Enum followed by getDeclaringClass
Class keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
查看是否包含某个值
containsValue方法的代码为:
public boolean containsValue(Object value) {
value = maskNull(value);
for (Object val : vals)
if (value.equals(val))
return true;
return false;
遍历值数组进行比较。
根据键删除
remove方法的代码为:
public V remove(Object key) {
if (!isValidKey(key))
return null;
int index = ((Enum)key).ordinal();
Object oldValue = vals[index];
vals[index] = null;
if (oldValue != null)
size--;
return unmaskNull(oldValue);
代码也很简单,就不解释了。
实现原理小结
以上就是EnumMap的基本实现原理,内部有两个数组,长度相同,一个表示所有可能的键,一个表示对应的值,值为null表示没有该键值对,键都有一个对应的索引,根据索引可直接访问和操作其键和值,效率很高。
本节介绍了EnumMap的用法和实现原理,用法上,如果需要一个Map且键是枚举类型,则应该用它,简洁、方便、安全,实现原理上,内部使用数组,根据键的枚举索引直接操作,效率很高。
下一节,我们来看枚举类型的Set接口的实现类EnumSet,与之前介绍的Set的实现类不同,它内部没有用对应的Map类EnumMap,而是使用了一种极为高效的方式,什么方式呢?
----------------
未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心原创,保留所有版权。