枚举和注解
34 用enum代替int常量
35 用实例域代替序数
36 用EnumSet代替位域
37 用EnumMap代替序数索引
有时你可能会见到利用 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;
}
@Override
public String toString() {
return name;
}
}
现在假设有一个香草的数组,表示一座花园的植物,你想要按照类型(一年生,多年生或者两年生植物)进行组织之后将这些植物列出来。
首先准备数据
final int NUM_PLANTS = 6;
Random random = new Random();
Plant[] garden = new Plant[NUM_PLANTS];
for (int num = 0; num < NUM_PLANTS; num++) {
Plant p = new Plant("植物("+num+")", Plant.LifeCycle.values()[random.nextInt(3)]);
garden[num] = p;
}
1、最容易想到的做法
构建三个集合,每种类型一个,并且遍历整个花园,然后将每种香草放在对应的集合中。这种做法没法弊端很多、很low、容易爆ArrayIndexOutOfBoundException,这里不做介绍
2、EnumMap实现
Map<Plant.LifeCycle, Set<Plant>> cycleSetMap = new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values()) {
cycleSetMap.put(lc,new HashSet<>());
}
for (Plant plant : garden) {
cycleSetMap.get(plant.lifeCycle).add(plant);
}
System.out.println(cycleSetMap);
上面这段程序简短,清楚,安全。不必手工标注这些索引的输出。EnumMap在运行速度方面之所以能与通过序数索引的数组相媲美。
3、stream实现
System.out.println(Arrays.stream(garden).collect(Collectors.groupingBy(p -> p.lifeCycle)));
这段代码的问题在于他选择自己的映射实现,实际上不会是一个EnumMap,因此与显示的EnumMap版本的空间及时间性能并不吻合。为了解决这个问题可以使用有三个参数形式的 Collectors.groupingBy 方法。它允许调用者利用 mapFactory 参数定义映射实现。
System.out.println(Arrays.stream(garden).collect(Collectors.groupingBy(p -> p.lifeCycle,
() -> new EnumMap<>(Plant.LifeCycle.class), toSet())));
基于 stream 的代码版本的行为和 EnumMap 版本稍有不同。EnumMap 版本总是每一个植物生命周期都设计一个嵌套映射,基于 stream 的版本则仅当花园中包含一种或多种植物带有该生命周期才会设计一个嵌套映射。
// 上面运行结果
{ANNUAL=[植物(0), 植物(1)], PERENNIAL=[植物(3), 植物(4), 植物(5)], BIENNIAL=[植物(2)]}
{PERENNIAL=[植物(3), 植物(4), 植物(5)], BIENNIAL=[植物(2)], ANNUAL=[植物(0), 植物(1)]}
{ANNUAL=[植物(0), 植物(1)], PERENNIAL=[植物(3), 植物(4), 植物(5)], BIENNIAL=[植物(2)]}