Effective Java(3rd)-Item37 使用EnumMap而不是序号索引

  偶尔,你可能会看到代码使用序号方法(item37 )对数组或列表进行索引。例如,考虑这个简单的类代表了一个植物:

image.png

  现在假设你有一个花园拥有一个植物数组,你想要列出这些按生命周期分类的植物(一年生,多年生,或两年一次)。为了达到这个效果,你构造了三个set,一个用于每个生命周期,在花园中迭代,将每个植物放置在合适的set上。有写程序员会通过将集合放入按生命周期序号索引的数据来做到这一点:


image.png

  这种技术有效,但是充满了问题。因为数组与泛型不兼容(item28)。程序需要一个未经检查的强制转换,将不会干净地编译。因为数组不知道它的索引代表了什么,你不得不手动标记输出。但是,这种技术最严重的问题是,当你访问由枚举的序号索引的数组时,使用正确的int值是你的责任;ints不提供枚举的类型安全。如果你使用了错误值,程序将安静地做了错误的事情,或者如果你足够幸运,将抛出ArrayIndexOutOfBoundsException。

  这里有一个更好的方式来实现相同的作用。数组实际上是从枚举到值的 映射,所以最好使用Map。更具体地说,这里有一个使用枚举key而设计的非常快的map,叫作java.util.EnumMap。当程序被重写未使用EnumMap时,如下所示:


image.png

  这个程序更短,更清晰,更安全,与原始速度相当。这里没有不安全的强制转换;不需要手动标记输入因为map的keys是枚举,它们知道如何转换它们,并可打印字符串;在计算数组索引时不可能出现错误。EnumMap的速度与序号索引相当的原因是EnumMap在内部使用了这样的数组,但它对程序员隐藏了实现细节,联合了Map的特性和类型安全以及数组的速度。注意到EnumMap构造器接受key类型的Class对象:这是一个 有界的类型标记,提供了运行时泛型类型信息(item33

  使用stream( item45 )来管理map,先前的程序会更短。这是基于stream的最简单代码,极大地复制了前面例子的行为:

image.png

  这串代码的问题在于,它选择了它自己map的实现,实际上它不会是EnumMap,所以它与显式的EnumMap不匹配版本的空间和时间性能。为了矫正这个问题,使用Collectors.groupingBy的三个参数的形式,允许调用方确定map的实现,使用mapFacotory参数:


image.png

  这种优化在像这样的玩具程序中是不值得做的,但是对于一个大量使用map的程序来说可能是至关重要的。
  基于stream的版本的行为与EnumMap版本略有不同,EnumMap版本总是为每个植物生命周期生成一个嵌套的map,基于stream的版本值只生成了一个嵌套版本,如果花园包含了一个或更多具有该生命周期的植物。所以,比如,如果花园包含了一年生植物和多年生植物但是没有两年期植物,plantsByLifeCycle的大小在EnumMap版本中将是3,在基于stream的两个版本中都是2.
  你可能会看到一个数组用序数索引(两次!)来表示来自两个枚举值的映射。比如,这个程序使用这样的数组来映射两相向相变(流体向固体是通过冷冻,流体向气体是煮,诸如此类):

  
image.png

  这个程序运行正常甚至显得优雅,但是外表是骗人的。想之前的简单花园例子看到的哪有,编译器没有办法值得序号和数组指数的关系。如果你在转化表中犯错了或忘记修改相或相的枚举,你的程序将在编译时失败。失败可能是ArrayIndexOutOfBoundsException,NullPointerException,或(更糟的是)沉默的错误行为。以及表的大小在阶段数上是二次方的,即使非空项的数目较小。
  同样,使用EnumMap你能做得更好。因为每个状态改变都基于一系列的状态枚举,最好将关系表示为从一个枚举(”从“阶段)到从第二个枚举(”到“阶段)到结果(相变)的映射。与相变相关联的两个阶段最好通过将它们与相变枚举相关联来捕获,然后使用初始化嵌套的EnumMap:


image.png

  初始化相变映射的代码有点复杂。映射的类型是Map<Phase,Map<Phase,Transition>>,这意味着”从(源)阶段映射到从(目标)阶段映射到过渡阶段。“此映射使用两个收集器的级联序列初始化的。第一个收集器通过源相分类,第二个从目标相映射创建了一个EnumMap。在第二个收集器()(x,y)->y)种的合并函数并未使用;之所以需要它,只是因为我们需要指定一个映射工厂才能获得EnumMap,收集器提供了可伸缩的工厂。本书的上一版使用显式迭代来初始化相变映射。代码更冗长,但可以说更容易理解。
  现在,假设你想要在这个系统中加上新的相:等离子体,或店里话气体。只有两个与此阶段相关的过渡:电离化,把气体运输到等离子体中;以及去化,将等离子体转化未气体。要更新基于数组的程序,你必须向阶段添加一个新的常量,向Phase.Transition添加两个新的常数,并用一个新的16元素版本替换原来的9元素数组。如果将太多或太少的元素添加到数组中,或者将元素放置得乱七八糟,你就不走运了:程序将会编译,但在运行时失败。基于EnumMap的版本更新,只需要将等离子体添加到相位列表中,将电离(气体,等离子体)和去电离(等离子体,气体)添加到相变列表中:

image.png

  这个程序负责所有其他的一切,几乎没有出错的机会。在内部映射是用数组来实现的,因此为了增加清晰度,安全性和易于维护,你只需在空间或时间上花费很少的费用。
  为了简洁起见,上面的示例用null来表示没有状态更改(其中往返是相同的)。这不是很好的实践,很可能在运行时导致NullPointweException.为这个问题设计一个干净而优雅的解决方案是令人惊讶的棘手的,由此产生的程序足够长以至于它们将本item中的主要材料中减少了。
  总之,几乎没有使用序数索引数组的使用场景:使用EnumMap代替。如果关系代表了多层面,使用EnumMap<...,EnumMap<...>>。使用Enum.ordinal是应用程序员很少应该遵守的一般原则的特例( item35)

本文写于2019.7.8,历时2天

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • [{"reportDate": "2018-01-23 23:28:49","fluctuateCause": n...
    加勒比海带_4bbc阅读 767评论 1 2
  • 目录: Android:Android 0.*Android 1.*Android 2.*Android 3.*A...
    敲代码的令狐葱阅读 3,826评论 0 2
  • 若我们的共鸣体不曾被发现 是不是也就不会有 那么多空想杂念 徘徊在人群边缘 在夜里书写秘密的想念 若我们的感情脆弱...
    JJJaeL阅读 250评论 0 1
  • 月亮还没圆,在板房的上空如半边括号。 我们在教室里,提前过中秋,提前做着月圆时做的事。 因为过中秋的时候有人回不了...
    如月如月阅读 435评论 9 8
  • 海醇是个正在读大二的学生,爽朗大方,敢爱敢恨,就是脾气有点不好,家住农村生活也还算过得去。看着身边的朋友一个个的一...
    小汉堡姐姐阅读 295评论 22 3