用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;}@Overridepublic String toString() {return name;}
}

  假设有一个香草的数组,表示一座花园中的植物,想要按照类型(一年生、多年生或者两年生植物)进行组织后将植物列出来。

  有些程序员会将这些集合放到一个按照类型序号进行索引的数组实现这一点。

// Using ordinal() to index an array - DON'T DO THIS
public static void main(String[] args) {Herb[] garden = {new Herb("a", Herb.Type.ANNUAL), new Herb("b", Herb.Type.BIENNIAL)};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: [a]
PERENNIAL: []
BIENNIAL: [b]

  这种方法可行,但是隐藏许多问题。但是由于数组与泛型不兼容,需要进行未受检的转换,并且不能正确无误的编译。因为数组不知道它的索引代表着什么,你必须手工标注这些索引的输出。但是这种方法最严重的问题在于,当你访问了一个按照枚举的序数进行索引的数组时,使用正确的int值就是你的责任了;int不能够提供枚举的类型安全。如果使用了错误的值,程序就会悄悄地完成错误的工作,或者幸运的话,会抛出ArraysIndexOutOfBoundException异常。

  幸运的是,有一种更好的方法可以达到同样的效果。数组实际上充当着从枚举到值的映射,因此可能还要用到Map。更具体的说,有一种非常快速的Map实现专门用于枚举键,称作Java.util.EnumMap。

public static void main(String[] args) {Herb[] garden = {new Herb("a", Herb.Type.ANNUAL), new Herb("b", Herb.Type.BIENNIAL)};// Using an EnumMap to associate data with an enumMap<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);
}
{ANNUAL=[a], PERENNIAL=[], BIENNIAL=[b]}

  这段代码更简短、更清楚,也更安全,运行速度方面可以与使用序数的程序相媲美。它没有不安全的转换;不必手工标注这些索引的输出,因为映射键应该知道如何将自身翻译成可打印的字符串的枚举;计算数组索引时也不可能出错。EnumMap在运行速度方面之所以能与通过序数索引的数组相媲美,是因为EnumMap在内部使用了这种数组。但是它对程序员隐藏了这种实现细节,集Map的丰富功能和类型安全与数组的快速于一身。注意EnumMap构造器采用键类型的Class对象:这是一个有限的类型令牌,它提供了运行时的泛型信息。

  你还可能见到按照序数进行索引(两次)的数组的数组,该序数表示两个枚举值的映射。例如下面的程序就是使用这样的一个数组将两个阶段映射到一个阶段过渡中(从液体到固体称作凝固,从液体到气体称作沸腾,诸如此类)。

//Using ordinal() to index array of arrays -DON'T DO IS
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()];}}
}

  这段代码可行,看起来也很优雅,但是事实并非如此。就像上面那个比较简单的香草花圆的示例一样,编译器无法知道序数和索引之间的关系。如果在过渡表中出了错,或者在修改Phase或者Phase.Transition枚举类型的时候忘记了将它更新,程序就会在运行时失败。这种失败的形式可能是ArrayIndexOutOfBoundsException、NullPointerException或者没有任何错误的提示的错误行为。这张表的大小是阶段个数的平方,即使非null项的数量比较少。

  同样,利用EnumMap依然可以做的更好一些。因为每个阶段的过度都是通过一对阶段枚举进行索引的,最好将这种关系表示为一个map,这个map的键是一个枚举,值为另一个map(起始阶段),这第二个map的键为第二个枚举(目标阶段),它的值为结果(阶段过渡),即形成了Map(起始阶段,Map(目标阶段,阶段过渡))这种形式。一个阶段过渡所关联的两个阶段,最好通过“数据与阶段过渡枚举之间的关联”来获取,之后用该阶段过渡枚举来初始化嵌套的EnumMap。

//Using a nested EnumMap to associate data with enum pairs
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的类型为Map<Phase,Map<Phase,Transition>>,表示是由键为源Phase(即第一个phase)、值为另外一个map组成的Map,其中组成值的Map是由键值对目标Phase(即第二个Phase)、Transition组成的。静态初始化代码块中的第二个循环初始化了外部map,得到了三个空的内容map。代码块中的第二个循环利用每个状态过渡常量提供的起始信息和目标信息初始化了内部map。代码块中的第二个循环利用每个状态过度常量提供的起始信息和目标信息初始化了内部的map。

  现阶段新增加一个新的阶段:plasma(离子)或者电离气体。只有两个过渡与这个阶段相关联:电离化,它将气体变成离子;以及消电离化,将离子变成气体。为了更新基于数组的程序,必须添加Phase添加一种新的常量,给Phase.Transition添加两种常量,用新的16个元素的版本取代原来9个元素的数组的数组。如果给数组添加过多或者过少,或者元素放置不妥当,可就麻烦了:程序可编译,但是会运行失败。为了更新EnumMap的版本,所要做的就是必须将PLASMA添加到Phase列表,并将IONSIE(GAS,PLASMA)和DEIONIZE(PLASMA,GAS)添加到Phase.Transition的列表中。程序会自行处理其他的事情,你几乎没有机会出错。从内部来看,Map的Map被实现成了数组的数组,因此提升了清楚行、安全性和易维护性的同时,在空间上或者时间上还几乎不用任何开销。

  总而言之,最好不要用序数来索引数组,而要使用EnumMap。如果你所表示的这种关系是多维的,就使用EnumMap<...,EnumMap<...>>。应用程序的程序员在一般情况下都不使用Enum.ordinal,即使要用也很少,因此这是一种特殊情况。

EnumMap实现

/**
* All of the values comprising K.  (Cached for performance.)
*/
private transient K[] keyUniverse;

转载于:https://www.cnblogs.com/mr-cc/p/5808478.html

用EnumMap代替序数索引相关推荐

  1. Effective Java~37. 用EnumMap 代替序数索引

    有时可能会看到使用 ordinal 方法(条目 35)来索引到数组或列表的代码. 例如,考虑一下这个简单的类来代表一种植物: class Plant {enum LifeCycle { ANNUAL, ...

  2. Effective Java 枚举和注解 第33条:用 EnumMap 代替序数索引

    有时候,你可能会见到利用 ordinal 方法(见第31条)来索引数组的代码.例如下面这个过于简化的类,用来表示一种烹饪用的香草: public class Hurb {public enum Typ ...

  3. Effective Java之EnumMap代替序数索引(三十三)

    Map的实现类有很多种,EnumMap从名字我们可以看出这个Map是给枚举类用的.它的key为枚举元素,value自定义.在工作中我们也可以用其他的Map来实现我们关于枚举的需求,但是为什么要用这个E ...

  4. 第 3 次读 Effective Java,这 58 个技巧最值!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:Dong GuoChao <Effective ...

  5. Java接地气日常编码技巧

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 本文来源:http://r6d.cn/9KG9 Effec ...

  6. 读完《Effective Java》后,总结了 50 条开发技巧

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | Dong GuoChao 来源 | https ...

  7. Effective Java读书笔记二:枚举和注解

    第30条:用enum代替int常量 当需要一组固定常量的时候,应该使用enum代替int常量,除了对于手机登资源有限的设备应该酌情考虑enum的性能弱势之外. 第31条:用实例域代替序数 枚举的ord ...

  8. java78条注意事项

    这78条来源于<Effective Java>一书,因中文版翻译得实在难道,我就只留了个目录. 创建和销毁对象 第1条:考虑用静态工厂方法代替构造器 第2条:遇到多个构造器参数时要考虑用构 ...

  9. 《Effective Java2》笔录

    转自:http://www.verydemo.com/demo_c89_i134810.html Joshua Bloch在国内出版的书包括<Effective Java2><Jav ...

最新文章

  1. CNI:容器网络接口
  2. 中文乱码,也许这个小技巧可以帮到你
  3. java xml特殊字符处理_dom4j特殊字符处理
  4. hdu As Easy As A+B
  5. C语言使用1到9求出所有k个数字的所有组合的算法(附完整源码)
  6. linux 双mysql_MySQL双主互备+Keepalived高可用架构实现案例
  7. myqsl cluster error code 2310
  8. git pull 报错:Untracked Fles Preventing Merge
  9. c语言程序做成可执行文件,windows环境下C程序生成可执行文件
  10. 数学界的花木兰——苏菲﹒热尔曼
  11. c/s三层结构信息系统的三个层次_如何使用ABP框架(2)三层架构与领域驱动设计的对比...
  12. Android 6.0 设备强制要求开启全盘加密
  13. 崩坏3支持鸿蒙系统没,崩坏3鸿蒙版
  14. 机器学习基础算法16- 决策树与随机森林-理论部分
  15. wifi分析仪怎么看哪个信道好_无线路由器设置选择哪条信道比较好
  16. 计算机桌面文字重影,电脑桌面字有重影怎么办
  17. 购买代购的产品算违法吗——看空姐代购被判刑有感
  18. Highcharts 江湖就这样
  19. 如何设置Luminati Proxy Manager? Luminati+VMlogin=多个( Facebook, Google, 亚马逊,ebay)等帐号同时登录,批量管理且不被关联。
  20. 使用loadrunner javavuser协议开发脚本实战

热门文章

  1. Codeforces Round #417 (Div. 2)
  2. Objective-C基础
  3. 两个数据库表同步的可视化WEB同步程序
  4. 管理Shader——Shader概览
  5. evolution 的回收站不能清除的终极解决办法
  6. 自己用as3实现的以多边形等几何模型为基础的碰撞系统 - 例子A
  7. ModelCoder中的代数环问题
  8. 2021-04-13 Linux I/O模型
  9. (106)System Verilog类中变量双向约束关系
  10. 三观要正 心态要好 要快乐