问:简单谈谈你对 EnumMap 的理解及其特点与应用场景?
答:EnumMap 是对 Map 接口的实现类,其 key-value 映射中的 key 是 Enum 类型,其原理是一个对象数组,数组的下标索引就是根据 Map 中的 key 直接获取(即枚举中的 ordinal 值),数组长度就是枚举类成员个数;当 key 为枚举类型时其效率比 HashMap 高,因为可以直接获取数组下标索引访问到元素;此外 EnumMap 是保证顺序的,输出是按照 key 在枚举中的顺序来确定的。
大多数时候使用 EnumMap 的场景都是 key-value 对存储中 key 为枚举类型的场景,不过需要注意 EnumMap 的构造方法,如下:
//需要传递一个类型信息,因为没有这个类信息就不知道具体的枚举类是什么,
// 也就无法初始化内部的数据结构。
public EnumMap(Class < K > keyType)
// 其他构造方法。
public EnumMap(EnumMap < K, ? extends V > m )
public EnumMap(Map < K, ? extends V > m )
所以 EnumMap 与 HashMap 的主要不同就是构造方法需要传递类型参数,此外 EnumMap 能依据 key 的枚举顺序保证有序性,当 key 为枚举类型时使用 EnumMap 的效率远远高于 HashMap。
问:简单说说 EnumMap 的实现原理?
答:EnumMap 的实现原理依赖内部两个长度相同的数组,一个表示所有可能的键,一个表示对应的值,当放入 key-value 时首先会检查键的类型,如果类型不对会抛出异常,否则调用 key 的 ordinal 获取索引 index,并将值 value 放入值数组 vals[index] 中(注意:如果值 value 为 null,则为了区别真正的 null 与没有值,EnumMap 会将 null 值包装成一个特殊的对象)。
其构造方法主要就是在初始化相关数组,如下:
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable {
//key键的具体枚举类型
private final Class<K> keyType;
// key的所有枚举值
private transient K[] keyUniverse;
// EnumMap的存储实现,仅仅为一个枚举成员个数长度的数组
private transient Object[] vals;
......
// 构造方法
public EnumMap(Class<K> keyType) {
// key的枚举类型赋值
this.keyType = keyType;
// 获取枚举类的所有枚举值存入数组缓存使用
keyUniverse = getKeyUniverse(keyType);
// 实例化枚举值个数长度的数组
vals = new Object[keyUniverse.length];
}
......
}
其 put 操作的源码如下:
public V put (K key, V value )
{
//检查key类型是不是构造时的类型
typeCheck(key);
// 获取枚举成员在枚举中的顺序位置
int index = key.ordinal();
// 放值或者替换 //(注意:这里如果value为null这会进行包装为Object和解包装操作)
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null) size++;
return unmaskNull(oldValue);
}
// value为null时包装成重写过toString和hashCode方法的Object
private Object maskNull (Object value ){
return (value == null ? NULL : value);
}
// value为null时从重写过toString和hashCode方法的Object解包装为null
private V unmaskNull (Object value ){
return (V) (value == NULL ? null : value);
}
其获取指定 key 的 value 的 get 方法实现如下:
//注意这里如果value为null这会进解包装操作,参见put
public V get (Object key ){
return (isValidKey(key) ? unmaskNull(vals[((Enum) key).ordinal()]) : null);
}