Java枚举反向查找最佳实践

Arm*_*and 72 java enums static-initializer

我在博客上看到它建议以下是使用getCode(int)Java枚举进行"反向查找"的合理方法:

public enum Status {
    WAITING(0),
    READY(1),
    SKIPPED(-1),
    COMPLETED(5);

    private static final Map<Integer,Status> lookup 
            = new HashMap<Integer,Status>();

    static {
        for(Status s : EnumSet.allOf(Status.class))
            lookup.put(s.getCode(), s);
    }

    private int code;

    private Status(int code) {
        this.code = code;
    }

    public int getCode() { return code; }

    public static Status get(int code) { 
        return lookup.get(code); 
    }
}
Run Code Online (Sandbox Code Playgroud)

对我来说,静态地图和静态初始化程序看起来都是个坏主意,我的第一个想法就是将查询编码为:

public enum Status {
    WAITING(0),
    READY(1),
    SKIPPED(-1),
    COMPLETED(5);

    private int code;

    private Status(int code) {
        this.code = code;
    }

    public int getCode() { return code; }

    public static Status get(int code) { 
        for(Status s : values()) {
            if(s.code == code) return s;
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

这两种方法都有明显的问题,是否有推荐的方法来实现这种查找?

小智 25

来自Google的Guava的Maps.uniqueIndex非常适合构建查找地图.

更新:以下是使用Maps.uniqueIndexJava 8 的示例:

public enum MyEnum {
    A(0), B(1), C(2);

    private static final Map<Integer, MyEnum> LOOKUP = Maps.uniqueIndex(
                Arrays.asList(MyEnum.values()),
                MyEnum::getStatus
    );    

    private final int status;

    MyEnum(int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }

    @Nullable
    public static MyEnum fromStatus(int status) {
        return LOOKUP.get(status);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 当然对于流你不需要番石榴:`LOOKUP = stream(values()).collect(toMap(MyEnum::getStatus, x -&gt; x));`。 (4认同)
  • 这是一个很好的答案,它确实摆脱了“静态”类初始化程序。有谁知道它相对于Java中的特定“ Map”而言是否还有其他优势(仅针对特定于字段的初始化器摆脱静态初始化器是不足以使我在类路径中包括一个库,即使该库是Guava )。 (2认同)

Mat*_*all 20

虽然它具有更高的开销,但静态映射很好,因为它提供了恒定时间查找code.您的实现的查找时间随着枚举中的元素数量线性增加.对于小型枚举,这根本不会有显着贡献.

这两种实现的一个问题(并且,可以说,通常使用Java枚举)是一个隐藏的额外值,它Status可以承担:null.根据业务逻辑的规则Exception,当查找"失败"时,返回实际的枚举值或抛出一个值是有意义的.

  • @jjnguy看看http://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/ O(1)是"一个总会在同一时间执行的算法(或空格),不管输入数据集的大小." 而O(N)是"一种算法,其性能将线性增长并与输入数据集的大小成正比",因此在这种情况下,数据集的大小不会随着运行而变化(为什么我认为你认为它是'常数'),算法的性能仍然基于输入数据集的大小(在这种情况下,枚举中的条目数) (11认同)
  • @jinguy,它们可能都是"常量"时间操作,但每次操作中的常量是不同的.一个是在哈希表中查找值的时间,另一个是循环变量(但在运行时是常量)值数组所需的时间.如果你在这个枚举中有一百万个值(不实用,只是一个例子),那么你更喜欢map-lookup选项. (6认同)
  • @jjnguy:声明算法是"O(n)"并不意味着`n`可以在运行时_改变.如果这对不可变(因此在运行时固定)列表执行类似查找会怎么样?这绝对是一个`O(n)`算法,其中`n`是列表的大小. (5认同)
  • @jjnguy:枚举大小是'O(n)`.这都是迂腐,因为枚举不太可能很大. (3认同)
  • @Matt,我相信两种方式都是恒定时间查找,因为Map中有一定数量的项目. (2认同)
  • @jjnguy:不。通过值数组进行查找需要检查列表中的每个元素。如果增加枚举中的元素数量,则增加可能要检查的反向查找元素数量。 (2认同)
  • 我认为在这种情况下我们应该比较真实世界枚举大小的绝对空间和时间特征,而不是"N→∞". (2认同)
  • 实际上,对于少于10个成员的典型枚举,线性扫描可能会快一点.但是,调用`values()`这会让它变得更慢. (2认同)
  • 我选择在我的静态查找方法中返回一个java.util.Optional &lt;MyEnumType&gt;。编写此答案时,此功能不可用,但可以使用户直接清楚查找可能会失败。也许您可以将其整合到您的答案中?我自己也可能会发布答案,但这似乎对您也很好。 (2认同)

Paŭ*_*ann 8

这是一个可能更快的替代方案:

public enum Status {
    WAITING(0),
    READY(1),
    SKIPPED(-1),
    COMPLETED(5);

    private int code;

    private Status(int code) {
        this.code = code;
    }

    public int getCode() { return code; }

    public static Status get(int code) {
        switch(code) {
            case  0: return WAITING;
            case  1: return READY;
            case -1: return SKIPPED;
            case  5: return COMPLETED;
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,如果您希望以后能够添加更多常量,那么这不是真正可维护的.

  • 但就清洁代码而言,这是一个绝对不可接受的解决方案.使用此解决方案,您有两个不同的位置,其中id映射到枚举值,因此当映射不同时可能存在错误! (15认同)
  • @jjnguy:不,编译器可以优化此开关以使用二进制搜索或查找表(取决于数字).而且你不需要在之前创建和填充`values()`数组(仅此一个就会使这个变量成为'O(n)`).当然,现在方法更长,因此加载它需要更长时间. (12认同)
  • 不是**完全相同,交换机版本可以使用查找表直接跳转到正确的代码而不是进行一系列测试:http://www.artima.com/underthehood/flowP.html (9认同)

dig*_*oel 6

显然,地图将提供恒定的时间查找,而循环则不会.在一个值很少的典型枚举中,我没有看到遍历查找的问题.