1. 程式人生 > >第三十三條 用EnumMap代替序數索引

第三十三條 用EnumMap代替序數索引

之前提過,我們可能會用ordinal()方法來求索引,代替一些陣列的索引,例如數種的例子,表示一些植物的歸類

public class Herb {     public enum Type { ANNUAL, PERENNIAL, BIENNIAL }          private final String name;     private final Type type;          Herb(String name, Type type) {         this.name = name;         this.type = type;     }          @Override     public String toString() {         return name;     } }

假設植物是按照一年 兩年 多年 植物進行羅列,那麼需要三個集合,每個集合裝自己對應的植物,把植物園中所有的植物便利一遍,按照要求放進去,書中程式碼

// 我新增的,假設有四種植物,分別是 aa  bb  cc  dd ,對應的的是列舉的三個型別值,分別代表 一年 兩年 多年,最終列印的   // 是 一年植物的集合  二年植物的集合  多年植物的集合 Herb[] garden = {new Herb("aa", Herb.Type.ANNUAL), new Herb("bb", Herb.Type.PERENNIAL), new Herb ("cc", Herb.Type.BIENNIAL), new Herb("dd", Herb.Type.ANNUAL)};

    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]);

上述程式碼沒問題,但有個可能出錯的地方就是泛型與陣列公用,我們知道,編譯時泛型生效,執行時擦除泛型,如果型別錯誤的話,會報錯的,這裡比較好的是,我們沒用錯,那就解讀一下這幾行程式碼吧。我們建立了個Set集合型別的陣列,就是說herbsByType[]陣列中的每個物件都是個Set集合,並且陣列中元素的個數是Herb.Type.values().length,值為3;然後便利這個陣列,往這個數組裡新增Set集合物件,此時 herbsByType[i] = new HashSet<Herb>(); 每個Set集合都是空集合;重點來了,整個程式碼中第二個增強for迴圈,便利了植物園的植物,也就是上面寫的garden陣列,有4個物件,拿第一個物件舉例,Herb a = new Herb("aa",Herb.Type.ANNUAL);增強for迴圈中 h 就是 Herb a,herbsByType[h.type.ordinal()].add(h); 先看h.type.ordinal(),我們知道意思就是Herb("aa", Herb.Type.ANNUAL)中的type,看最上面的程式碼,type即為Herb中的列舉Type,也就是Herb.Type.ANNUAL的ordinal值,那麼ANNUAL是列舉中的第一個,ordinal值為0,看到這,終於明白了,herbsByType[h.type.ordinal()].add(h); 就是說 herbsByType[0].add(a),即herbsByType[0].add(new Herb("aa", Herb.Type.ANNUAL)),然後增強for迴圈還有後面三個物件,同理,herbsByType[1].add(new Herb("bb", Herb.Type.PERENNIAL)),herbsByType[2].add(new Herb("cc",Herb.Type.BIENNIAL)),herbsByType[0].add(new Herb("aa", Herb.Type.ANNUAL))。我們繼續往下看,看第三個增強for,意思是遍歷herbsByType[]陣列,Herb.Type.values()[i] 意思依次列印 Herb 列舉的物件,herbsByType[i] 意思是對應的Set集合,Set集合中裝的是Herb列舉,列舉重寫了toString()方法,列印的是name,即 aa bb cc dd。最終列印結果符合咱們的預期: ANNUAL: [dd, aa] PERENNIAL: [bb] BIENNIAL: [cc]

上述分析了一堆,看起來也比較麻煩,書中給出了另外一種寫法。之前介紹了EnumSet,屬於Set集合;今天介紹EnumMap,當然屬於Map集合。

Herb[] garden = {new Herb("aa", Herb.Type.ANNUAL), new Herb("bb", Herb.Type.PERENNIAL), new Herb ("cc", Herb.Type.BIENNIAL),new Herb("dd", Herb.Type.ANNUAL)};              Map<Herb.Type, Set<Herb>> herbByType = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);     for(Herb.Type t : Herb.Type.values())         herbByType.put(t, new HashSet<Herb>());     for(Herb h : garden)         herbByType.get(h.type).add(h);     System.out.println(herbByType);

這種寫法,更安全,不必要泛型手動輸入索引計算等等,也沒有不安全轉換,意思和上面的寫法一樣,只是在這裡,用了Map集合代替了之前的陣列,上面程式碼是陣列中嵌套了Set集合,陣列用索引來區分植物的年限;這裡是用Map集合,key值為植物年限,value值為Set集合,除此之外,其他的都一樣,列印結果為 {ANNUAL=[dd, aa], PERENNIAL=[bb], BIENNIAL=[cc]}

EnumMap 是屬於Map集合,class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>, 看到這,我們回想之前的EnumSet,就會發現用法有異曲同工之妙,只要熟悉了Set和Map,EnumSet和EnumMap的用法也就差不多了,無外乎多了點私有的api和屬性,並且記住,集合比陣列安全。

如果我們碰到兩次使用序數索引的陣列,即二維陣列,怎麼變,看下面例子

public enum Phase {     SOLID, LIQUID, GAS;     public enum Transition {         MELT, FREEZE, BOTL, CONDENSE, SUBLIME, DEPOSIT;         private static final Transition[][] TRANSITIONS = {             {null, MELT, SUBLIME},             {FREEZE, null, BOTL},             {DEPOSIT, CONDENSE, null}         };         public static Transition from(Phase src, Phase dst) {             return TRANSITIONS[src.ordinal()][dst.ordinal()];         }     } }

這段程式碼的意思是說,要進行二維陣列組合,通過方法去取對應的值,看起來沒問題,但實際上如果和第一個案例差不多,並且如果新增想新的植物,擴充套件不太方便,同理,可以使用EnumMap來修改,

public enum Phase {     SOLID, LIQUID, GAS;          public enum Transition {         MELT(SOLID,LIQUID), FREEZE(LIQUID, SOLID),         BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),         SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);                  private final Phase src;         private final Phase dst;                  Transition(Phase src, Phase dst) {             this.src = src;             this.dst = dst;         }                  private static final Map<Phase, Map<Phase, Transition>> m =                 new EnumMap<Phase, Map<Phase, Transition>>(Phase.class);         static {             for(Phase p : Phase.values())                 m.put(p, new EnumMap<Phase, Transition>(Phase.class));             for(Transition t : Transition.values())                 m.get(t.src).put(t.dst, t);         }                  public static Transition from(Phase src, Phase dst) {             return m.get(src).get(dst);         }     } }

這種修改方法,有點類似第二十九條中的泛型案例,把可能出現的情況,全都列舉了出來,然後把他們裝進一個map集合,然後map集合巢狀集合,這個好處就是避免了陣列,不用擔心型別異常和資料越界等問題。在列舉中,也要記得,優先使用EnumMap和EnumSet,避免使用陣列。