概要
EnumMap是專門為枚舉類型量身定做的Map實現(xiàn)。
示例
先來看個示例代碼:
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定義和數(shù)據(jù)結(jié)構(gòu)
類定義如下:
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的數(shù)組,key即枚舉類的值對象,包括了name和ordinal兩個屬性
private transient K[] keyUniverse;
// 存儲value的數(shù)組,value允許null,null會被轉(zhuǎn)換成Object NULL實例替代存儲
private transient Object[] vals;
private transient int size = 0;
// Object的實例,用于代表null,用于區(qū)分值數(shù)組中元素本身是null(還未存值),
// 還是存儲的就是null值(已經(jīng)存值)
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);
}
// 其他省略
}
根據(jù)類定義,存儲結(jié)構(gòu)圖如下:

EnumMap采用兩個獨立的數(shù)組分別維護key和value。由于EnumMap的key必須為指定的枚舉類的類型,而枚舉類下的值數(shù)量和ordinal(聲明次序)已經(jīng)固定,因此數(shù)組的容量大小在構(gòu)造方法中就已經(jīng)固定了。key存在keyUniverse數(shù)組指定的下標(biāo)中,value也存在vals相同的下標(biāo)中。這樣key和value就建立了邏輯上的關(guān)系,便于get和put。
如果存一個null值,如下,則null會做特殊處理,轉(zhuǎn)成Object NULL實例后存儲,這樣是為了區(qū)分索引下標(biāo)的元素是否已經(jīng)映射,沒有映射則為null,映射為null了則用NULL實例進行占位替換。
put(Color.blue, null)
基本操作
EnumMap的基本操作都比較快,都在常量時間內(nèi)完成。
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(聲明次序值)作為數(shù)組的索引下標(biāo)。
3、將value存到數(shù)組key的下標(biāo)里面。如果是null元素轉(zhuǎn)換為NULL實例存儲。
4、更新元素數(shù)量計數(shù)器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的類型是否為相應(yīng)枚舉類的類型,key為null或者類型不匹配返回null。
2、用key的ordinal值作為數(shù)組的索引下標(biāo),查找元素并返回,如果為NULL實例,則轉(zhuǎn)換為null后返回。
總結(jié)
EnumMap是專門為枚舉類型量身定做的Map實現(xiàn)。雖然使用其它的Map實現(xiàn)(如HashMap)也能完成枚舉類型實例到值得映射,但是使用EnumMap會更加高效:它只能接收同一枚舉類型的實例作為鍵值,并且由于枚舉類型實例的數(shù)量相對固定并且有限,所以EnumMap使用數(shù)組來存放與枚舉類型對應(yīng)的值。這使得EnumMap的效率非常高。EnumMap在內(nèi)部使用枚舉類型的ordinal()得到當(dāng)前實例的聲明次序,并使用這個次序維護枚舉類型實例對應(yīng)值在數(shù)組的位置。
1、父類為AbstractMap,未實現(xiàn)Map接口,只實現(xiàn)了Cloneable和Serializable接口。
2、非線程安全,所有方法和操作都未加鎖。
3、采用key數(shù)組和vals數(shù)組共同實現(xiàn)key和value的關(guān)聯(lián)。
4、不允許null key,但允許null value。
5、null值會被轉(zhuǎn)換為Object的NULL實例占位替換。
6、元素的存儲順序按照枚舉值的聲明次序存儲。

