【问题标题】:How can I lookup a Java enum from its String value?如何从其 String 值中查找 Java 枚举?
【发布时间】:2010-11-08 00:43:38
【问题描述】:

我想从它的字符串值(或任何其他值)中查找一个枚举。我已经尝试了以下代码,但它不允许在初始化程序中使用静态。有没有简单的方法?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};

【问题讨论】:

  • IIRC,这给出了 NPE,因为静态初始化是自上而下完成的(即顶部的枚举常量是在它下降到 stringMap 初始化之前构造的)。通常的解决方案是使用嵌套类。
  • 感谢大家如此迅速的反应。 (FWIW 我没有发现 Sun Javadocs 对这个问题很有用)。
  • 这确实是语言问题而不是库问题。但是,我认为 API 文档的阅读量比 JLS 多(尽管可能不是由语言设计者阅读),所以这样的内容在 java.lang 文档中应该更突出。

标签: java enums lookup


【解决方案1】:
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}

【讨论】:

    【解决方案2】:

    如果您想要一个默认值并且不想构建查找映射,您可以创建一个静态方法来处理它。 此示例还处理预期名称以数字开头的查找。

        public static final Verbosity lookup(String name) {
            return lookup(name, null);
        }
    
        public static final Verbosity lookup(String name, Verbosity dflt) {
            if (StringUtils.isBlank(name)) {
                return dflt;
            }
            if (name.matches("^\\d.*")) {
                name = "_"+name;
            }
            try {
                return Verbosity.valueOf(name);
            } catch (IllegalArgumentException e) {
                return dflt;
            }
        }
    

    如果您需要它作为次要值,您只需像其他一些答案一样首先构建查找图。

    【讨论】:

      【解决方案3】:

      从 java 8 开始,您可以在一行中初始化地图,而无需静态块

      private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                       .collect(Collectors.toMap(Enum::toString, Function.identity()));
      

      【讨论】:

      • +1 对流的出色使用,非常简洁而不牺牲可读性!我之前也没有见过Function.identity() 的这种用法,我发现它在文字上比e -&gt; e 更具吸引力。
      【解决方案4】:

      如果对他人有帮助,我更喜​​欢的选项(此处未列出)使用Guava's Maps functionality

      public enum Vebosity {
          BRIEF("BRIEF"),
          NORMAL("NORMAL"),
          FULL("FULL");
      
          private String value;
          private Verbosity(final String value) {
              this.value = value;
          }
      
          public String getValue() {
              return this.value;
          }
      
          private static ImmutableMap<String, Verbosity> reverseLookup = 
                  Maps.uniqueIndex(Arrays.asList(Verbosity.values()), Verbosity::getValue);
      
          public static Verbosity fromString(final String id) {
              return reverseLookup.getOrDefault(id, NORMAL);
          }
      }
      

      默认情况下,您可以使用null,您可以使用throw IllegalArgumentException 或您的fromString 可以返回Optional,无论您喜欢什么行为。

      【讨论】:

        【解决方案5】:

        使用 Java 8,您可以通过以下方式实现:

        public static Verbosity findByAbbr(final String abbr){
            return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
        }
        

        【讨论】:

        • 我会抛出异常而不是返回null。但这也取决于用例
        【解决方案6】:

        您可以将 Enum 定义为以下代码:

        public enum Verbosity 
        {
           BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
           private int value;
        
           public int getValue()
           {
             return this.value;
           } 
        
           public static final Verbosity getVerbosityByValue(int value)
           {
             for(Verbosity verbosity : Verbosity.values())
             {
                if(verbosity.getValue() == value)
                    return verbosity ;
             }
        
             return ACTION_NOT_VALID;
           }
        
           @Override
           public String toString()
           {
              return ((Integer)this.getValue()).toString();
           }
        };
        

        See following link for more clarification

        【讨论】:

          【解决方案7】:

          @Lyle 的回答相当危险,我发现如果您将枚举设为静态内部类,它尤其不起作用。相反,我使用了类似的东西,它将在枚举之前加载 BootstrapSingleton 映射。

          编辑 现代 JVM(JVM 1.6 或更高版本)应该不再是问题,但我确实认为 JRebel 仍然存在问题,但我没有机会重新测试它

          先加载我:

             public final class BootstrapSingleton {
          
                  // Reverse-lookup map for getting a day from an abbreviation
                  public static final Map<String, Day> lookup = new HashMap<String, Day>();
             }
          

          现在在枚举构造函数中加载它:

             public enum Day { 
                  MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
                  THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;
          
                  private final String abbreviation;
          
                  private Day(String abbreviation) {
                      this.abbreviation = abbreviation;
                      BootstrapSingleton.lookup.put(abbreviation, this);
                  }
          
                  public String getAbbreviation() {
                      return abbreviation;
                  }
          
                  public static Day get(String abbreviation) {
                      return lookup.get(abbreviation);
                  }
              }
          

          如果你有一个内部枚举,你可以在枚举定义之上定义 Map 并且(理论上)应该在之前加载。

          【讨论】:

          • 需要定义“危险”以及为什么内部类实现很重要。这更像是一个自上而下的静态定义问题,但在另一个相关答案中有工作代码,并链接回这个问题,该问题在 Enum 实例本身上使用静态 Map,并使用从用户定义的值到的“get”方法枚举类型。 stackoverflow.com/questions/604424/lookup-enum-by-string-value
          • @DarrellTeague 最初的问题是由于旧的 JVM(1.6 之前)或其他 JVM(IBM)或热代码交换器(JRebel)造成的。这个问题不应该发生在现代 JVM 中,所以我可能会删除我的答案。
          • 请注意,由于自定义编译器实现和运行时优化确实有可能...排序可能会重新排序,从而导致错误。但是,这似乎违反了规范。
          【解决方案8】:

          你已经接近了。对于任意值,请尝试以下操作:

          public enum Day { 
          
              MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
              THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;
          
              private final String abbreviation;
          
              // Reverse-lookup map for getting a day from an abbreviation
              private static final Map<String, Day> lookup = new HashMap<String, Day>();
          
              static {
                  for (Day d : Day.values()) {
                      lookup.put(d.getAbbreviation(), d);
                  }
              }
          
              private Day(String abbreviation) {
                  this.abbreviation = abbreviation;
              }
          
              public String getAbbreviation() {
                  return abbreviation;
              }
          
              public static Day get(String abbreviation) {
                  return lookup.get(abbreviation);
              }
          }
          

          【讨论】:

          • 由于类加载器问题,此方法无法可靠运行。我不推荐它,而且我经常看到它失败,因为查找图在被访问之前还没有准备好。您需要将“查找”放在枚举之外或另一个类中,以便它首先加载。
          • @Adam Gent:下面有一个指向 JLS 的链接,其中以这个确切的结构为例。它说静态初始化从上到下发生:docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
          • 是的,像 java 8 这样的现代 java 已经修复了内存模型。话虽如此,如果由于循环引用而嵌入初始化,像 jrebel 这样的热插拔代理仍然会搞砸。
          • @Adam Gent:嗯。我每天都学到新东西。 [悄悄地用引导单例重构我的枚举...]对不起,如果这是一个愚蠢的问题,但这主要是静态初始化的问题吗?
          • 我从未见过静态代码块未初始化的问题,但自 2015 年开始使用 Java 以来,我只使用过 Java 8。我只是使用 JMH 1.22 做了一个小基准测试并使用用于存储枚举的 HashMap&lt;&gt; 被证明比 foo 循环快 40% 以上。
          【解决方案9】:

          您可以按照上面 Gareth Davis 和 Brad Mace 的建议使用 Enum::valueOf() 函数,但请确保处理 IllegalArgumentException,如果使用的字符串不存在于枚举中,则会抛出该函数。

          【讨论】:

            【解决方案10】:

            也许,看看这个。它为我工作。 这样做的目的是用“/red_color”查找“RED”。 如果enums 很多,则声明static map 并将enums 仅加载一次会带来一些性能优势。

            public class Mapper {
            
            public enum Maps {
            
                COLOR_RED("/red_color", "RED");
            
                private final String code;
                private final String description;
                private static Map<String, String> mMap;
            
                private Maps(String code, String description) {
                    this.code = code;
                    this.description = description;
                }
            
                public String getCode() {
                    return name();
                }
            
                public String getDescription() {
                    return description;
                }
            
                public String getName() {
                    return name();
                }
            
                public static String getColorName(String uri) {
                    if (mMap == null) {
                        initializeMapping();
                    }
                    if (mMap.containsKey(uri)) {
                        return mMap.get(uri);
                    }
                    return null;
                }
            
                private static void initializeMapping() {
                    mMap = new HashMap<String, String>();
                    for (Maps s : Maps.values()) {
                        mMap.put(s.code, s.description);
                    }
                }
            }
            }
            

            请发表你的意见。

            【讨论】:

              【解决方案11】:

              使用为每个 Enum 自动创建的 valueOf 方法。

              Verbosity.valueOf("BRIEF") == Verbosity.BRIEF
              

              对于任意值以:

              开头
              public static Verbosity findByAbbr(String abbr){
                  for(Verbosity v : values()){
                      if( v.abbr().equals(abbr)){
                          return v;
                      }
                  }
                  return null;
              }
              

              只有在探查器告诉您之后才继续执行 Map。

              我知道它会遍历所有值,但只有 3 个枚举值几乎不值得付出任何其他努力,事实上,除非你有很多值,否则我不会为 Map 而烦恼,它会足够快。

              【讨论】:

              • 谢谢 - 如果我知道值仍然不同,我可以使用大小写转换
              • 您可以直接访问类成员“abbr”,因此可以使用“v.abbr.equals...”代替“v.abbr()”。
              • 另外,对于任意值(第二种情况)考虑抛出 IllegalArgumentException 而不是返回 null (ie 找不到匹配项) - 这就是 JDK @无论如何,987654325@ 做到了。
              • @Gareth Davis 我认为EnumSet.allOf(Verbosity.class) 可能比values() 更好,后者每次都会在堆上创建一个新数组!
              • @Adam Gent 反过来会在堆上创建一个对象......非常怀疑你是否会注意到除了非常敏感的代码之外的任何差异
              【解决方案12】:

              你不能使用valueOf()

              编辑:顺便说一句,没有什么能阻止你在枚举中使用 static { }。

              【讨论】:

              • 或者枚举类上的合成valueOf,所以不需要指定枚举类Class
              • 当然,它只是没有 javadoc 条目,所以很难链接到。
              • 您可以使用 valueOf() 但名称必须与枚举声明中设置的名称相同。上述两种查找方法中的任何一种都可以修改为使用 .equalsIgnoringCase() 并且对错误的鲁棒性稍强。
              • @leonardo 是的。如果它增加了鲁棒性或只是容错是有争议的。在大多数情况下,我会说是后者,在这种情况下,最好在其他地方处理它并仍然使用 Enum 的 valueOf()。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-03-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-17
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多