概要
EnumMap是專門為枚舉類型量身定做的Map實(shí)現(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, "藍(lán)色");
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:藍(lán)色
black:黑色
yellow:黃色
green:綠色
{red=紅色, blue=藍(lán)色, 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實(shí)例替代存儲
private transient Object[] vals;
private transient int size = 0;
// Object的實(shí)例,用于代表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采用兩個獨(dú)立的數(shù)組分別維護(hù)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實(shí)例后存儲,這樣是為了區(qū)分索引下標(biāo)的元素是否已經(jīng)映射举塔,沒有映射則為null绑警,映射為null了則用NULL實(shí)例進(jìn)行占位替換求泰。
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實(shí)例存儲培廓。
4、更新元素數(shù)量計(jì)數(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實(shí)例,則轉(zhuǎn)換為null后返回晨横。
總結(jié)
EnumMap是專門為枚舉類型量身定做的Map實(shí)現(xiàn)洋腮。雖然使用其它的Map實(shí)現(xiàn)(如HashMap)也能完成枚舉類型實(shí)例到值得映射,但是使用EnumMap會更加高效:它只能接收同一枚舉類型的實(shí)例作為鍵值手形,并且由于枚舉類型實(shí)例的數(shù)量相對固定并且有限啥供,所以EnumMap使用數(shù)組來存放與枚舉類型對應(yīng)的值。這使得EnumMap的效率非常高库糠。EnumMap在內(nèi)部使用枚舉類型的ordinal()得到當(dāng)前實(shí)例的聲明次序伙狐,并使用這個次序維護(hù)枚舉類型實(shí)例對應(yīng)值在數(shù)組的位置。
1曼玩、父類為AbstractMap鳞骤,未實(shí)現(xiàn)Map接口,只實(shí)現(xiàn)了Cloneable和Serializable接口黍判。
2豫尽、非線程安全,所有方法和操作都未加鎖顷帖。
3美旧、采用key數(shù)組和vals數(shù)組共同實(shí)現(xiàn)key和value的關(guān)聯(lián)渤滞。
4、不允許null key榴嗅,但允許null value妄呕。
5、null值會被轉(zhuǎn)換為Object的NULL實(shí)例占位替換嗽测。
6绪励、元素的存儲順序按照枚舉值的聲明次序存儲。