概要
EnumMap是专门为枚举类型量身定做的Map实现。
示例
先来看个示例代码:
public class EnumTest {
public enum Color {
red, blue, black, yellow, green
}
public static void main(String[] args) {
EnumMap<Color,String> map = new EnumMap<>(Color.class);
map.put(Color.yellow, "黄色");
map.put(Color.blue, "蓝色");
map.put(Color.red, "红色");
map.put(Color.black, "黑色");
map.put(Color.green, "绿色");
for(Map.Entry<Color,String> entry : map.entrySet()){
System.out.println(entry.getKey()+":"+entry.getValue());
}
System.out.println(map);
}
}
输出:
red:红色
blue:蓝色
black:黑色
yellow:黄色
green:绿色
{red=红色, blue=蓝色, black=黑色, yellow=黄色, green=绿色}
从输出来看,迭代的顺序与Color枚举类型下定义的值顺序一致。
EnumMap定义和数据结构
类定义如下:
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable {
// 枚举类的class对象,例如示例的Color.class
private final Class<K> keyType;
// 存储key的数组,key即枚举类的值对象,包括了name和ordinal两个属性
private transient K[] keyUniverse;
// 存储value的数组,value允许null,null会被转换成Object NULL实例替代存储
private transient Object[] vals;
private transient int size = 0;
// Object的实例,用于代表null,用于区分值数组中元素本身是null(还未存值),
// 还是存储的就是null值(已经存值)
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);
}
@SuppressWarnings("unchecked")
private V unmaskNull(Object value) {
return (V)(value == NULL ? null : value);
}
// 其他省略
}
根据类定义,存储结构图如下:
EnumMap采用两个独立的数组分别维护key和value。由于EnumMap的key必须为指定的枚举类的类型,而枚举类下的值数量和ordinal(声明次序)已经固定,因此数组的容量大小在构造方法中就已经固定了。key存在keyUniverse数组指定的下标中,value也存在vals相同的下标中。这样key和value就建立了逻辑上的关系,便于get和put。
如果存一个null值,如下,则null会做特殊处理,转成Object NULL实例后存储,这样是为了区分索引下标的元素是否已经映射,没有映射则为null,映射为null了则用NULL实例进行占位替换。
put(Color.blue, null)
基本操作
EnumMap的基本操作都比较快,都在常量时间内完成。
1、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);
}
private void typeCheck(K key) {
Class<?> keyClass = key.getClass();
if (keyClass != keyType && keyClass.getSuperclass() != keyType)
throw new ClassCastException(keyClass + " != " + keyType);
}
步骤:
1、检查key的类型是否是枚举类的类型,否则抛出ClassCastException异常。
2、用key的ordinal(声明次序值)作为数组的索引下标。
3、将value存到数组key的下标里面。如果是null元素转换为NULL实例存储。
4、更新元素数量计数器size。
2、get方法
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
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;
}
步骤:
1、检查key的类型是否为相应枚举类的类型,key为null或者类型不匹配返回null。
2、用key的ordinal值作为数组的索引下标,查找元素并返回,如果为NULL实例,则转换为null后返回。
总结
EnumMap是专门为枚举类型量身定做的Map实现。虽然使用其它的Map实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用EnumMap会更加高效:它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值。这使得EnumMap的效率非常高。EnumMap在内部使用枚举类型的ordinal()得到当前实例的声明次序,并使用这个次序维护枚举类型实例对应值在数组的位置。
1、父类为AbstractMap,未实现Map接口,只实现了Cloneable和Serializable接口。
2、非线程安全,所有方法和操作都未加锁。
3、采用key数组和vals数组共同实现key和value的关联。
4、不允许null key,但允许null value。
5、null值会被转换为Object的NULL实例占位替换。
6、元素的存储顺序按照枚举值的声明次序存储。