Java:检查枚举是否包含给定字符串?

259

以下是我的问题 - 我正在寻找(如果存在的话)ArrayList.contains(); 方法的枚举等效形式。

这里是我代码问题的示例:

enum choices {a1, a2, b1, b2};

if(choices.???(a1)}{
//do this
} 

现在我意识到使用ArrayList来存储Strings可能更好,但是我必须在其他地方通过switch/case运行我的枚举内容。因此就产生了问题。

假设这样的解决方案不存在,我该如何处理呢?


从Java 7开始,可以使用字符串实现Switch/case。 - Andrey
32个回答

320

使用Apache commons lang3库替代

 EnumUtils.isValidEnum(MyEnum.class, myValue)

46
对于有兴趣的人,请注意,它们使用的基本实现只是 try/catch 解决方案(@since 3.0 @version $Id: EnumUtils.java 1199894 2011-11-09 17:53:59Z ggregory $)。 - jgawrych
2
让我的代码更简单,所以我不在乎它是否使用异常来控制流程(实际上它们不应该这样做)...如果他们改变了这一点,那就太好了。 - jpangamarca
1
返回已翻譯文本:src - https://github.com/apache/commons-lang/blob/3a818ed6a833f083a2db9bb6804c1bdb43b9b0ec/src/main/java/org/apache/commons/lang3/EnumUtils.java#L89 - muttonUp
2
Guava是否也包含像这样的解决方案? - Cypress Frankenfeld
此时此刻,这应该是被接受的答案 :-). - Josue Abarca
我该如何检查fileName? public enum ModulacaoFilesEnum { FILE001("FILE 001.xlsx"), FILE002("FILE 002.xlsx"), FILE003("FILE 003.xlsx"); @Getter private final String fileName;ModulacaoFilesEnum(String fileName) { this.fileName = fileName; }} - Mateus Nogueira

262
这应该可以解决问题:
public static boolean contains(String test) {

    for (Choice c : Choice.values()) {
        if (c.name().equals(test)) {
            return true;
        }
    }

    return false;
}

这种方式的意思是,您无需担心以后添加其他枚举值,因为它们都经过了检查。

编辑:如果枚举值非常大,您可以将其放入HashSet中:

public static HashSet<String> getEnums() {

  HashSet<String> values = new HashSet<String>();

  for (Choice c : Choice.values()) {
      values.add(c.name());
  }

  return values;
}

那么您只需要执行以下操作:values.contains("your string"),它将返回真或假。


17
那是一个非常糟糕的实现:Choice.valueOf(test) 是你想要的(使用 try/catch)。 - bestsss
21
bestsss,这显然是最合适的解决方案。通过抛出异常来实现一种exists()方法类型是不良实践。尽管您可能认为由于它不看起来像O(n),所以您的实现更有效率,但在不可见的底层框架中却是O(n)。而且使用try {} catch会增加开销。此外,这样做只是不够简洁美观。 - jluzwick
27
@Jared,绝对使用valueOf方法。只需捕获异常并返回false即可。对于那些持有不同观点的人,如果你查看实现,它已经使用了Map,并且JDK开发人员有更好的机会去优化这个过程。该API抛出异常,这是一个有争议的做法(而不是返回null),但是当你处理抛出异常的API时,就按照它来做,不要重复造轮子。 - Yishai
2
@bestsss 我会承认,使用 try {} catch 的方式进行优化并增加可忽略的开销,但是在你的示例中使用 try {} catch 通常被认为是不好的实践,请参见 https://dev59.com/JHA75IYBdhLWcg3wBkO1 。由于我们可以保证内部Java代码的相同性能而不引入任何错误,因此我们应该采用上述方法。这是因为它比使用 try {} catch 更清晰、更简洁地控制程序流程。 - jluzwick
28
为什么要优先使用contains()而不是valueOf()呢?因为“异常通常用于只在异常情况下使用;它们永远不应该用于普通控制流”(Joshua Bloch,《Effective Java》)。 - james.garriss
显示剩余11条评论

69

您可以使用 Enum.valueOf() 方法

enum Choices{A1, A2, B1, B2};

public class MainClass {
  public static void main(String args[]) {
    Choices day;

    try {
       day = Choices.valueOf("A1");
       //yes
    } catch (IllegalArgumentException ex) {  
        //nope
  }
}

如果您预计检查失败的频率很高,最好使用其他人展示的简单循环 - 如果您的枚举包含许多值,可以构建一个将枚举值转换为字符串并查询该HashSet或类似对象的HashSet


17
在这种情况下,我认为异常不是最佳选择。 - GokcenG
7
尝试和捕获应该是最后的选择。尝试和捕获太昂贵了。 - Jesus Dimrix
4
依靠运行时异常来执行业务逻辑,除了昂贵之外,还不够易读。对于已检查异常,则不同,因为它们是业务的一部分。 - Luís Soares
2
这也可以防止任何人打开广泛的异常断点来查找正在重试的实际异常情况(或者至少使这样做非常烦人)。只有在出现异常情况时才使用异常。 - Nickolay Kondratyev
4
EnumUtils.isValidEnum(MyEnum.class, myValue)使用类似的逻辑。在我看来,为这种琐碎的任务添加整个库是有意义的。 - Vikramjit Roy
1
点赞。对于这段代码的反对实际上是毫无根据的。 - Fran Marzoa

57
如果您使用的是Java 1.8,您可以选择Stream + Lambda来实现这个功能:
public enum Period {
    DAILY, WEEKLY
};

//This is recommended
Arrays.stream(Period.values()).anyMatch((t) -> t.name().equals("DAILY1"));
//May throw java.lang.IllegalArgumentException
Arrays.stream(Period.values()).anyMatch(Period.valueOf("DAILY")::equals);

就性能而言,它与@richard-h的for循环解决方案存在相同的问题。 - Klesun

26

Guava的Enums可能是你的朋友

比如像这样:

enum MyData {
    ONE,
    TWO
}

@Test
public void test() {

    if (!Enums.getIfPresent(MyData.class, "THREE").isPresent()) {
        System.out.println("THREE is not here");
    }
}

怎么样可以检查字符串(FILE 001.xlsx) FILE001("FILE 001.xlsx"), FILE002("FILE 002.xlsx"), FILE003("FILE 003.xlsx"); - Mateus Nogueira

19

更好的是:

enum choices {
   a1, a2, b1, b2;

  public static boolean contains(String s)
  {
      for(choices choice:values())
           if (choice.name().equals(s)) 
              return true;
      return false;
  } 

};

感谢您提供乐观的解决方案。 - Parth Patel

17

这里提到了几个库,但我错过了我真正要找的一个:Spring!

有一个ObjectUtils#containsConstant库,默认情况下不区分大小写,但如果您想要严格一点,也可以使用。它的用法如下:

if(ObjectUtils.containsConstant(Choices.values(), "SOME_CHOISE", true)){
// do stuff
}
注意:我在这里使用了重载方法来演示如何进行区分大小写的检查。您可以省略布尔值以实现不区分大小写的行为。
但是,对于大型枚举,请小心,因为它们不像某些枚举使用Map实现...
作为一个额外奖励,它还提供了valueOf的不区分大小写变体:ObjectUtils#caseInsensitiveValueOf

15

Java流提供了优雅的方法来实现这一点

Stream.of(MyEnum.values()).anyMatch(v -> v.name().equals(strValue))

返回值:如果流中有任何元素与提供的值匹配,则为true,否则为false


15
您可以先将枚举类型转换为列表,然后使用列表的 contains 方法。
enum Choices{A1, A2, B1, B2};

List choices = Arrays.asList(Choices.values());

//compare with enum value 
if(choices.contains(Choices.A1)){
   //do something
}

//compare with String value
if(choices.contains(Choices.valueOf("A1"))){
   //do something
}

这应该是被接受的答案。将其转换为列表是最干净的方法。这样可以避免在其他答案中讨论Java中“异常的正确使用”的整个(侧面)讨论。 - Manuel
3
如果该值不存在,抛出IllegalArgumentException异常。 - mkyong

12

几个假设:
1)没有try/catch,因为它是异常流程控制
2)'contains'方法必须快速,因为它通常运行多次。
3)空间不受限制(对于普通解决方案很常见)

import java.util.HashSet;
import java.util.Set;

enum Choices {
    a1, a2, b1, b2;

    private static Set<String> _values = new HashSet<>();

    // O(n) - runs once
    static{
        for (Choices choice : Choices.values()) {
            _values.add(choice.name());
        }
    }

    // O(1) - runs several times
    public static boolean contains(String value){
        return _values.contains(value);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接