第三十七条:不要以序号作为索引,使用EnumMap代替

ops/2024/9/22 23:15:04/

我们有时可能会看到使用ordinal方法来索引数组或者列表的代码。例如,考虑到下面这个简单的类,用于表示一种植物:

public class Plant {enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL}final String name;final LifeCycle lifeCycle;public Plant(String name, LifeCycle lifeCycle) {this.name = name;this.lifeCycle = lifeCycle;}@Overridepublic String toString() {return name;}
}

现在假设有一个表示花园的植物数组,我们想按照生长周期(一年、多年生或者两年生)列出这些植物。要做到这点,我们要构建三个集合,每个生长周期一个,然后遍历整个花园,将每个植物放入相应的集合中。有些程序员会这么做:把这些集合放在一个数组中,以生长周期的序号来索引。

public static void main(String[] args) {List<Plant> garden = new ArrayList<>();garden.add(new Plant("oneYear", Plant.LifeCycle.ANNUAL));garden.add(new Plant("twoYears", Plant.LifeCycle.PERENNIAL));garden.add(new Plant("twoYears", Plant.LifeCycle.PERENNIAL));garden.add(new Plant("manyYears", Plant.LifeCycle.BIENNIAL));garden.add(new Plant("manyYears", Plant.LifeCycle.BIENNIAL));garden.add(new Plant("oneYear", Plant.LifeCycle.ANNUAL));// 不推荐使用ordinal()Set<Plant>[] plantsOrder = new Set[Plant.LifeCycle.values().length];for (int i = 0; i < plantsOrder.length; i++) {plantsOrder[i] = new HashSet<>();}for (Plant p : garden) {plantsOrder[p.lifeCycle.ordinal()].add(p);}for (int i = 0; i < plantsOrder.length; i++) {System.out.printf("%s:%s\n", Plant.LifeCycle.values()[i], plantsOrder[i]);}System.out.println(plantsOrder);}

打印:

ANNUAL:[oneYear, oneYear]
PERENNIAL:[twoYears, twoYears]
BIENNIAL:[manyYears, manyYears]
[Ljava.util.Set;@23ab930dProcess finished with exit code 0

如上虽然可行,但有许多问题

  • 数组不能与泛型共用,存在Unchecked异常
  • 数组不知道索引代表的意义(如上0存储的为ANNUAL枚举),需要人为去处理

而使用EnumMap可避免上述问题,以Enum为键进行存储,而不是以它的ordinal(),如果是 多维的,可以使用 EnumMap<…,EnumMap<…>>

Map<Plant.LifeCycle, Set<Plant>> plantsOrder = new EnumMap<>(Plant.LifeCycle.class);for (Plant.LifeCycle type : Plant.LifeCycle.values()) {plantsOrder.put(type, new HashSet<>());}for (Plant p : garden) {plantsOrder.get(p.lifeCycle).add(p);}System.out.println(plantsOrder);

打印:

{ANNUAL=[oneYear, oneYear], PERENNIAL=[twoYears, twoYears], BIENNIAL=[manyYears, manyYears]}Process finished with exit code 0

注意:enumMap构造器接收的是其键的类型对应的class对象:这是一个有限制的类型令牌,它提供了运行时的泛型信息。

有时候我们还会看到使用序号来索引的数组的数组(序号用了两次),用来表示来自两个枚举值的映射。例如下面的程序,它使用了这样的一个数组来将两个相映射到一个相变(从液态到固态是凝固,从液态到气态是沸腾的,等等):

// 不推荐这么做
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()];}}public static void main(String[] args) {System.out.println(Transition.from(GAS,LIQUID ));}
}

打印:

CONDENSEProcess finished with exit code 0

这段代码的意思是说,要进行二维数组组合,通过方法去取对应的值,看起来没问题,但实际上如果和第一个案例差不多,并且如果添加想新的植物,扩展不太方便,同理,可以使用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);}}public static void main(String[] args) {System.out.println(Transition.from(GAS, SOLID));}}

打印:

DEPOSITProcess finished with exit code 0

我们也可以使用嵌套的enumMap实现添加一个新的相

 SOLID, LIQUID, GAS, PLASMA;public enum Transition {MELT(SOLID,LIQUID), FREEZE(LIQUID, SOLID),BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID),IONIZE(GAS,PLASMA),DEIONZE(PLASMA,GAS);...//其他代码不变

总而言之,序号不太适合用来索引数组,应该使用enumMap代替。程序员应该极少或绝对不使用ordinal方法。

所有文章无条件开放,顺手点个赞不为过吧!

                                                            


http://www.ppmy.cn/ops/114462.html

相关文章

开源 AI 智能名片链动 2+1 模式 O2O 商城小程序在社群活动中的应用与时机选择

摘要&#xff1a;本文探讨了开源 AI 智能名片链动 21 模式 O2O 商城小程序在社群经济中的重要性&#xff0c;着重分析了如何借助该小程序适时举办大型活动以维持和引爆社群活跃度。通过对活动时机选择的研究&#xff0c;强调了针对社群用户量身定制活动时机的必要性&#xff0c…

Foundation 折叠列表

Foundation 折叠列表 引言 在网页设计和开发中,折叠列表是一种常见且实用的界面元素,它允许用户展开和收起内容以节省空间并提高用户体验。Foundation 是一个流行的前端框架,它提供了一套强大的工具和组件来帮助开发者快速构建响应式和现代化的网页。本文将详细介绍如何在…

最优化理论与自动驾驶(十一):基于iLQR的自动驾驶轨迹跟踪算法(c++和python版本)

最优化理论与自动驾驶&#xff08;四&#xff09;&#xff1a;iLQR原理、公式及代码演示 之前的章节我们介绍过&#xff0c;iLQR&#xff08;迭代线性二次调节器&#xff09;是一种用于求解非线性系统最优控制最优控制最优控制和规划问题的算法。本章节介绍采用iLQR算法对设定…

分享一个 在线拍卖系统 商品竞拍平台Java、python、php三个技术版本(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

Spark-ShuffleWriter-UnsafeShuffleWriter-钨丝内存分配

一、上下文 《Spark-ShuffleWriter-UnsafeShuffleWriter》中提到在进行Page内存分配时&#xff0c;调用了一行代码 MemoryBlock page memoryManager.tungstenMemoryAllocator().allocate(acquired); 这里就会走MemoryManager的钨丝内存分配&#xff0c;下面我们来详细看下 …

华为HarmonyOS地图服务 5 - 利用UI控件和手势进行地图交互

场景介绍 本章节将向您介绍如何使用地图的手势。 Map Kit提供了多种手势供用户与地图之间进行交互&#xff0c;如缩放、滚动、旋转和倾斜。这些手势默认开启&#xff0c;如果想要关闭某些手势&#xff0c;可以通过MapComponentController类提供的接口来控制手势的开关。 接口…

某思CMS V10存在SQL注入漏洞

Fofa: product"魅思-视频管理系统" 框架:ThinkPHP 5,6 1 漏洞分析&复现 位于 /controller/Api.php 控制器中的getOrderStatus 方法POST传入&#xff0c;然后直接拼接了 orderSn 变量到 where 查询中&#xff0c;导致漏洞产生. /** * 查询订单支付状态 */ pub…

LeetCode337. 打家劫舍III

// 很好的一道题目&#xff0c;既考察递归又考察动归 // 这个版本超时了&#xff0c;原因是暴搜 // 很显然这里使用的是前序&#xff0c;那是不是应该考虑后序&#xff1f;public int rob(TreeNode root) {if (root null) {return 0;}if (root.left null && root.rig…