从字符串转换为具有大量值的 Java 枚举 [重复]

Posted

技术标签:

【中文标题】从字符串转换为具有大量值的 Java 枚举 [重复]【英文标题】:Convert from String to a Java enum with large amount of values [duplicate] 【发布时间】:2015-02-26 11:31:55 【问题描述】:

假设我有一个包含 100 个值的枚举。为简单起见,举个例子:

public enum code

    CODE_1("string1"),
    CODE_2("string2"),
    CODE_3("string3"),
    CODE_4("string4"),
    ...

我想创建一个公共方法来将已知格式的字符串(如“string1”、“string2”...)转换为适当的枚举值 CODE_1、CODE_2... 通常这是通过遍历所有值来完成的,如果找到匹配项,则返回该枚举值。 (详情可见in this question。)

但是,我担心定期循环所有值。这可能是一个巨大的瓶颈吗?如果不是 100 个元素,而是 1000 个呢?

作为我自己的练习,我尝试使用静态映射优化此查找,它可以确保给定任何字符串的 O(1) 查找时间。我喜欢这个额外的噱头,但如果确实有必要,我只想将它包含在我的代码中。您对使用迭代法与地图法有何想法和发现?

public enum Code

    ...
    //enum values
    ...


    //The string-to-Code map
    private static final Map<String,Code> CODE_MAP = populateMap();

    private static Map<String,Code> populateMap()
    
        Map<String,Code> map = new HashMap<String,Code>();

        for(Code c : Code.values())
        
            map.put(c.getCode(), c);
        

        return map;
    


    private String code;

    private Code(String code)
    
        this.code = code;
    

    public String getCode()
    
        return this.code;
    

    public Code convertFromString(String code)
    
        //assume that the given string is actually a key value in the map

        return (Code) CODE_MAP.get(code);
    

【问题讨论】:

您是否分析过并发现幼稚的迭代方式是一个瓶颈?如果还没有,则无需担心 - 将注意力集中在实际上是瓶颈的代码上。 如问题所述,这纯粹是假设性的练习。这并不意味着预优化。不过感谢您的关心 您没有发现任何证据表明 Java vakueOf 方法太慢了。所以这是重复的。 注意:我关闭这个问题的原因是事实上,valueOf 使用了Map,因此是正确的方法。 @RealSkeptic 我真的很失望。 ValueOf 与这个问题无关,因为 valueof 仅适用于与枚举名称匹配的字符串。这个问题开头提供的示例清楚地显示了字符串名称与枚举代码名称非常不同的情况。这与 Raedwald 将此问题标记为重复时所应用的错误推理相同。在这种情况下你不能使用 valueOf! 【参考方案1】:

您想要一个Map&lt;String, Code&gt;,但是如何整齐地填充它?枚举不允许您在初始化枚举实例之前初始化静态字段,但有一个巧妙的小技巧,称为 Initialization-on-demand holder idiom,它可以轻松实现此功能所需的静态初始化映射:

public enum Code 
    CODE_1("string1"),
    CODE_2("string2"),
    CODE_3("string3"),
    // etc
    ;

    private static class Holder 
        static Map<String, Code> CODE_MAP = new HashMap<>();
    

    private final String code;

    private Code(String code) 
        this.code = code;
        Holder.CODE_MAP.put(code, this);
    

    public String getCode() 
        return this.code;
    

    public Code convertFromString(String code) 
        return Holder.CODE_MAP.get(code);
    

这是因为类加载器在初始化枚举类之前初始化了内部静态类,因此在枚举实例初始化期间分配映射准备加载。

没有循环。没有特殊的代码来加载地图(在构造函数中完成)。最少的代码。

【讨论】:

所以你是说我在上面的代码中填充它的方式不是“整洁”?你能解释为什么它不“整洁”吗?您还说“枚举不允许您在初始化枚举实例之前初始化静态字段”,但我不认为我正在这样做。我在枚举实例之后填充地图? @user1884155 你的代码不整洁,因为如果它有很多东西 - 额外的方法,更多的行。这个 cude 仅使用 1 行进行地图初始化(如果算上 class Holder 行,则使用 2 行)并仅使用 1 行来加载它,没有定义额外的方法 - 将其称为“整洁”似乎是合理的。至于不允许静态初始化的枚举,我的意思是你不能这样做:private static Map&lt;String, Code&gt; CODE_MAP = new HashMap&lt;&gt;(); - 如果你这样做并尝试使用它,它将在枚举实例初始化期间为空,所以你不能像我一样整齐地加载它来自枚举的构造函数。 @Bohemian 内部静态类是绝对必要的吗? How to do reverse lookup in enums 中的类似解决方案使映射 private static final 直接在枚举中。 @greg 您的链接articke 的代码使用静态块来填充地图,这是普通静态地图所必需的,因为静态地图要到所有枚举实例之后 才会初始化已初始化 - 为时已晚,无法使用构造函数自行加载每个实例,这更整洁。由于一些原因,静态块也被认为是不良风格,一个是如果您将静态块移到地图声明上方,它将编译但会抛出 NPE。 @RubioRic 是的!好皮卡。更正了缺少的 Holder 限定符。干杯【参考方案2】:

Map 是不错的选择:更简洁的代码和O(1)。如果你使用for-loop,那么你得到的最好的就是O(n)

【讨论】:

【参考方案3】:

您提供的解决方案是正确的实施。

因为您只需要公开一种方法并且它更具可读性。

使用Map 而不是iterating it manually 总是好的。

正如你提到的,复杂性是O(1)

+1 to your question,因为它给出了a cleaner approach to use enum in some usecases

【讨论】:

【参考方案4】:

如果您的字符串代码值是已知且一致的格式,您可以避免使用 Map 及其消耗的内存并即时构建 CODE 枚举查找值:

  public static Code convertFromString(String code) 
    return valueOf("CODE_" + code.substring("string".length()));
  

【讨论】:

是的,格式不是真的那么一致,我只是用这个易于解析的例子来问这个问题。【参考方案5】:

好吧,映射解决方案的替代方案是巨大的开关语句(可以自动生成)或二进制搜索包含字符串的数组。我认为两者都不会大幅超越 HashMap 的性能,但如果它真的很重要,你可能最好通过基准测试。

没有提到的一件事是 Enum.valueOf() 如何让您将 String 转换为枚举值,如果它具有枚举成员之一的确切名称。如果您的情况完全有可能(仅看您的示例,我看不出 Code.CODE_1 不能轻易重命名 Code.string1 等),我建议使用它,因为它不需要额外的编码完全没有,因此将是最容易理解的。

【讨论】:

以上是关于从字符串转换为具有大量值的 Java 枚举 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

Java将字符串转换为枚举列表[重复]

将 Typescript 枚举从字符串转换为数字(角度)[重复]

枚举值返回为数字 [重复]

将枚举类型传递给具有整数值的转换器[重复]

Java 8:将具有字符串值的映射转换为包含不同类型的列表

如何在特定列中添加具有相同字符串值的行,并且不转换数据框? [重复]