Java集合框架:EnumMap
EnumMap定義
package java.util;import java.util.Map.Entry;
import sun.misc.SharedSecrets;
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>implements java.io.Serializable, Cloneable{private final Class<K> keyType;private transient K[] keyUniverse;private transient Object[] vals;private transient int size = 0;
} ??keyType變量是EnumMap的key泛型的類對象,EnumMap依據這個類型。能夠獲得keyUniverse的內容。vals存放的是與keyUniverse映射的值。假設沒有映射則為null,假設映射為null則會特殊處理成NULL。NULL的定義例如以下:
private static final Object NULL = new Object() {public int hashCode() {return 0;}public String toString() {return "java.util.EnumMap.NULL";}}; ??對于值NULL的處理相似WeakHashMap的特殊處理,會有兩個方法:
private Object maskNull(Object value) {return (value == null ? NULL : value);}private V unmaskNull(Object value) {return (V) (value == NULL ? null : value);} ??這樣能夠區分vals中是null(即沒有映射)還是NULL(即映射為null);
??EnumMap的size是依據vals中的非null(包含NULL)的值的個數確定的,比方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推斷key的類對象或者父類對象是否與keyType相等,假設不相等則拋出ClassCastException異常。
??注意EnumMap并沒有相似HashMap的resize的過程,也沒有載入因子的概念,由于在一個EnumMap創建的時候,keyUniverse和vals的大小就固定。
EnumMap使用
??先舉個小樣例:
package collections.map;import java.util.EnumMap;
import java.util.Map;public class EnumMapTest
{public enum Color{RED,BLUE,BLACK,YELLOW,GREEN;}public static void main(String[] args){EnumMap<Color,String> map = new EnumMap<>(Color.class);EnumMap<Color,String> map = new EnumMap<>(Color.class);map.put(Color.YELLOW, "黃色");map.put(Color.RED, "紅色");map.put(Color.BLUE, null);
// map.put(null, "無"); //會報NullPonitException的錯誤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:null
BLACK:黑色
YELLOW:黃色
GREEN:綠色
{RED=紅色, BLUE=null, BLACK=黑色, YELLOW=黃色, GREEN=綠色} ??EnumMap的key不同意為null,value能夠為null,依照key在enum中的順序進行保存。非線程安全。能夠用工具類Collections進行包裝成線程安全的:
Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...)); ??有關enum的應用知識能夠參考《Java枚舉類型enum》。
??EnumMap的基本操作都比較快,都在常量時間內完畢,基本上(但不保證)比HashMap快。
??EnumMap有三個構造函數:
- public EnumMap(Class<K> keyType);
- public EnumMap(EnumMap<K, ?
extends V> m);
- public EnumMap(Map<K, ?
extends V> m) ;
??前兩個構造函數一目了然,對第三個構造函數進行分析:
Map<Integer,Integer> map1 = new HashMap<>();map1.put(1, 1);map1.put(3, 3);map1.put(2, 2);Map<Integer,Integer> map2 = new EnumMap<>(map1);//編譯器提示錯誤:Cannot infer type arguments for EnumMap<> ??這個是由于Integer并非extends Enum;
??這里變換一下,採用Map
Map<Enum,Integer> map1 = new HashMap<>();map1.put(Color.YELLOW, 1);map1.put(Color.RED, 3);map1.put(Color.BLUE, 2);Map<Enum,Integer> map2 = new EnumMap<>(map1);for(Map.Entry entry:map2.entrySet()){System.out.println(entry.getKey()+":"+entry.getValue());}System.out.println(map2);System.out.println(map2.size()); ??能夠正常執行。輸出結果:
RED:3
BLUE:2
YELLOW:1
{RED=3, BLUE=2, YELLOW=1}
3 ??相信大家能夠總結個一二了吧。
EnumMap用途
??《Effective Java》中作者建議用EnumMap取代敘述索引。最好不要用序數來索引數組,而要使用EnumMap。
??這里採用《Effective Java》書中的樣例來舉例。
public static class Herb{public enum Type{ANNUAL, PERENNIAL, BIENNTAL}private final String name;private final Type type;public Herb(String name, Type type){this.name = name;this.type = type;}public Type getType(){return type;}@Overridepublic String toString(){return name;}} ??如今用一座種滿香草的花園,想要依照類型(一年生、多年生、兩年生,即上面Type的類型)進行組織之后將這些植物列出來。假設使用數組實現的話。須要構建三個集合,每種類型一個。而且遍歷整座花園,將每種香草放到相應的集合中。
Herb[] garden = new Herb[]{new Herb("f1",Herb.Type.ANNUAL),new Herb("f2",Herb.Type.PERENNIAL),new Herb("f3",Herb.Type.BIENNTAL),new Herb("f4",Herb.Type.PERENNIAL),new Herb("f5",Herb.Type.ANNUAL),new Herb("f6",Herb.Type.BIENNTAL),new Herb("f7",Herb.Type.ANNUAL),new Herb("f8",Herb.Type.BIENNTAL),new Herb("f9",Herb.Type.PERENNIAL)};Set<Herb>[] herbsByType = (Set<Herb>[]) new Set[Herb.Type.values().length];for(int i=0;i<herbsByType.length;i++){herbsByType[i] = new HashSet<Herb>();}for(Herb h:garden){herbsByType[h.type.ordinal()].add(h);}for(int i=0;i<herbsByType.length;i++){System.out.printf("%s:%s%n", Herb.Type.values()[i],herbsByType[i]);} ??執行結果:
ANNUAL:[f5, f7, f1]
PERENNIAL:[f4, f2, f9]
BIENNTAL:[f8, f3, f6] ??這樣的方法確實可行。可是影藏著很多問題。由于數組不能和泛型兼容。程序須要進行未受檢的轉換,而且不能正確無誤地進行編譯。由于數組不知道它的索引代表著什么,你必須手工標注這些索引的輸出。可是這樣的方法最嚴重的問題在于。當你訪問一個依照枚舉的敘述進行索引的數組時,使用正確的int值就是你的職責了。int不能提供枚舉的類型安全。
??可是你能夠用EnumMap改善這個程序:
Herb[] garden = new Herb[]{new Herb("f1",Herb.Type.ANNUAL),new Herb("f2",Herb.Type.PERENNIAL),new Herb("f3",Herb.Type.BIENNTAL),new Herb("f4",Herb.Type.PERENNIAL),new Herb("f5",Herb.Type.ANNUAL),new Herb("f6",Herb.Type.BIENNTAL),new Herb("f7",Herb.Type.ANNUAL),new Herb("f8",Herb.Type.BIENNTAL),new Herb("f9",Herb.Type.PERENNIAL)};Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<>(Herb.Type.class);for(Herb.Type t : Herb.Type.values()){herbsByType.put(t, new HashSet<Herb>());}for(Herb h:garden){herbsByType.get(h.type).add(h);}System.out.println(herbsByType); ??執行結果:
{ANNUAL=[f7, f1, f5], PERENNIAL=[f4, f2, f9], BIENNTAL=[f8, f6, f3]} ??這段程序更剪短、更清楚,也更安全。執行速度方面能夠與使用序數的數組相媲美。注意EnumMap構造器採用鍵類型的Class對象:這是一個有限制的類型令牌,它提供了執行時的泛型信息。
總結
??EnumMap是專門為枚舉類型量身定做的Map實現。
盡管使用其他的Map實現(如HashMap)也能完畢枚舉類型實例到值得映射,可是使用EnumMap會更加高效:它僅僅能接收同一枚舉類型的實例作為鍵值。而且由于枚舉類型實例的數量相對固定而且有限,所以EnumMap使用數組來存放與枚舉類型相應的值。這使得EnumMap的效率很高。EnumMap在內部使用枚舉類型的ordinal()得到當前實例的聲明次序,并使用這個次序維護枚舉類型實例相應值在數組的位置。
參考資料:
1. 《Java枚舉類型enum》
2. 《Effective Java(Second Edition)》. Joshua Bloch.
3. 《EnumMap與Enumset的使用 》
轉載于:https://www.cnblogs.com/yangykaifa/p/7388563.html
總結
以上是生活随笔為你收集整理的Java集合框架:EnumMap的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Luogu P1087 FBI树
- 下一篇: 星星还亮着几颗是什么歌啊?