zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java EnumMap 代替序数索引

JAVA索引 代替 序数
2023-09-11 14:22:55 时间

学习笔记《Effective Java 中文版 第2版》

经常会碰到使用Enum的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;
    }

    @Override public String toString() {
        return name;
    }
}

现在假设有一个香草的数组,表示一座花园中的植物,你想要按照类型(一年生、多年生或者两年生植物)进行组织之后再将这些植物列出来。如果要这么做的话,需要构建三个集合,每种类型一个,并且遍历整座花园,将每种香草放到相应的集合中。有些程序员会将这些集合放到一个按照类型的序数进行索引的数组来实现这一点。

//Using ordinal() to index an array - DON'T DO THIS
Herb[] garden = ... ;

//Indexed by Herb.Type.ordinal()
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);
}

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

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

java.util.EnumMap是一种非常快速的Map实现专门用于枚举的键。

//Using an EnumMap to associate data with an enum
Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);

for(Herb.Type t : Herb.Type.values)
    herbsByType.put(t, new HashSet<Herb>());

for(Herb h : garden)
    herbsByType.get(h.type).add(h);

System.out.println(herbsByType);

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