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

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

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

        现在假设你有一组植物代表一个花园,想要列出这些由生命周期组织的植物 (一年生,多年生,或双年生)。为此,需要构建三个集合,每个生命周期作为一个,并遍历整个花园,将每个植物放置在适当的集合中。

// Using ordinal() to index into an array - DON'T DO THIS!
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycle.length; i++)
	plantsByLifeCycle[i] = new HashSet<>();

for (Plant p : garden)
	plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);

// Print the results
for (int i = 0; i < plantsByLifeCycle.length; i++) {
	System.out.printf("%s: %s%n",Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}

        这种方法是有效的,但充满了问题。 因为数组不兼容泛型(条目 28),程序需要一个未经检查的转换,并且不会干净地编译。 由于该数组不知道索引代表什么,因此必须手动标记索引输出。 但是这种技术最严重的问题是,当你访问一个由枚举序数索引的数组时,你有责任使用正确的 int 值; int 不提供枚举的类型安全性。 如果你使用了错误的值,程序会默默地做错误的事情,如果你幸运的话,抛出一个 ArrayIndexOutOfBoundsException 异常。

        有一个更好的方法来达到同样的效果。 该数组有效地用作从枚举到值的映射,因此不妨使用 Map 。 更具体地说,有一个非常快速的 Map 实现,设计用于枚举键,称为 java.util.EnumMap 。 下面是当程序重写为使用EnumMap 时的样子:

// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);

for (Plant.LifeCycle lc : Plant.LifeCycle.values())
	plantsByLifeCycle.put(lc, new HashSet<>());

for (Plant p : garden)
	plantsByLifeCycle.get(p.lifeCycle).add(p);
	
System.out.println(plantsByLifeCycle);

        这段程序更简短,更清晰,更安全,运行速度与原始版本相当。 没有不安全的转换; 无需手动标记输出,因为map 键是知道如何将自己转换为可打印字符串的枚举; 并且不可能在计算数组索引时出错。 EnumMap 与序数索引数组的速度相当,其原因是 EnumMap 内部使用了这样一个数组,但它对程序员的隐藏了这个实现细节,将 Map 的丰富性和类型安全性与数组的速度相结合。 请注意, EnumMap 构造方法接受键类Class 型的 Class 对象:这是一个有限定的类型令牌(bounded type token),它提供运行时的泛型类型信息(条目 33)。

        通过使用 stream (条目 45)来管理 Map ,可以进一步缩短以前的程序。 以下是最简单的基于 stream 的代码,它们在很大程度上重复了前面示例的行为:

// Naive stream-based approach - unlikely to produce an EnumMap!
System.out.println(Arrays.stream(garden)
            .collect(groupingBy(p -> p.lifeCycle)));

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

// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden)
		.collect(groupingBy(p -> p.lifeCycle,() -> new EnumMap<>(LifeCycle.class), toSet())));

        这样的优化在像这样的示例程序中是不值得的,但是在大量使用 Map 的程序中可能是至关重要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值