【问题标题】:Check if enum exists in Java检查Java中是否存在枚举
【发布时间】:2009-07-22 20:16:45
【问题描述】:

是否有通过将枚举与给定字符串进行比较来检查枚举是否存在?我似乎找不到任何这样的功能。我可以尝试使用valueOf 方法并捕获异常,但我被告知捕获运行时异常并不是一个好习惯。有人有什么想法吗?

【问题讨论】:

  • 我真的不明白 emum valueOf 抛出异常背后的想法......这没有任何意义。如果它只返回 NULL,那么在各个方面都会更加实用。
  • @marcolopes:一个原因是想用 Enum 覆盖所有可能的情况。如果未找到 Enum,则意味着应尽快通知开发人员缺少案例。它不应该让程序在代码后面的其他地方抛出 NullPointerError。
  • @EricDuminil null 结果会更简单......这就是我所做的!我基本上不在我的代码上使用 valueOf,而是在 enum get(value) 中编写一个新方法来捕获异常...

标签: java string enums compare


【解决方案1】:

如果我需要这样做,我有时会创建一个 Set<String> 的名称,甚至是我自己的 Map<String,MyEnum> - 然后你可以检查一下。

有几点值得注意:

  • 在静态初始化程序中填充任何此类静态集合。 不要使用变量初始化器,然后依赖它在枚举构造函数运行时执行——它不会! (枚举构造函数是首先要执行的,在静态初始化之前。)
  • 尽量避免频繁使用values() - 它每次都必须创建和填充一个新数组。要遍历所有元素,请使用EnumSet.allOf,这对于没有大量元素的枚举来说效率更高。

示例代码:

import java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    private static final Map<String, SampleEnum> nameToValueMap =
        new HashMap<String, SampleEnum>();
    
    static {
        for (SampleEnum value : EnumSet.allOf(SampleEnum.class)) {
            nameToValueMap.put(value.name(), value);
        }
    }
    
    public static SampleEnum forName(String name) {
        return nameToValueMap.get(name);
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}

当然,如果您只有几个名字,这可能是矫枉过正——当 n 足够小时,O(n) 解决方案通常会胜过 O(1) 解决方案。这是另一种方法:

import java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    // We know we'll never mutate this, so we can keep
    // a local copy.
    private static final SampleEnum[] copyOfValues = values();
    
    public static SampleEnum forName(String name) {
        for (SampleEnum value : copyOfValues) {
            if (value.name().equals(name)) {
                return value;
            }
        }
        return null;
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}

【讨论】:

  • values() 创建并填充一个新数组?我不记得以前听说过,但大概你有消息来源?
  • 嗯,它返回一个数组......它不能保护你不改变那个数组......而且它不会阻碍后续的调用者。除此之外,请查看 JRE 源代码 :)
  • 糟糕,抱歉 - 是的,刚刚检查了自己。然后反编译一个枚举 :) 它使用 clone() 可能非常快,但不如根本不需要这样做那么快......
  • 是否有关于“大小”枚举从基于 Map 的方法与数组/循环方法中受益的指南? (我不是在寻找硬性/快速规则,只是在考虑何时考虑采取一种方式与另一种方式的一般情况)
  • @cdeszaq:我怀疑 JVM 和情况之间可能会有很大差异。如果您担心的事情可能在您的应用程序中很重要,我建议您进行性能测试。很抱歉不能提供更多帮助:(
【解决方案2】:

我认为没有一种内置方法可以在不捕获异常的情况下执行此操作。你可以改用这样的东西:

public static MyEnum asMyEnum(String str) {
    for (MyEnum me : MyEnum.values()) {
        if (me.name().equalsIgnoreCase(str))
            return me;
    }
    return null;
}

编辑: 正如 Jon Skeet 所说,values() 每次调用时都会克隆一个私有的后备数组。如果性能很关键,您可能只想调用一次values(),缓存数组,然后遍历它。

此外,如果您的枚举具有大量值,则 Jon Skeet 的 map 替代方法可能比任何数组迭代执行得更好。

【讨论】:

    【解决方案3】:

    我最喜欢的库之一:Apache Commons。

    EnumUtils 可以轻松做到这一点。

    以下示例使用该库验证枚举:

    public enum MyEnum {
        DIV("div"), DEPT("dept"), CLASS("class");
    
        private final String val;
    
        MyEnum(String val) {
        this.val = val;
        }
    
        public String getVal() {
        return val;
        }
    }
    
    
    MyEnum strTypeEnum = null;
    
    // test if String str is compatible with the enum 
    // e.g. if you pass str = "div", it will return false. If you pass "DIV", it will return true.
    if( EnumUtils.isValidEnum(MyEnum.class, str) ){
        strTypeEnum = MyEnum.valueOf(str);
    }
    

    【讨论】:

    • 是的。 Apache Commons 是完整的厨房;只有当您从中获得全部或最大利益时才值得:) 方轮:P
    • 在我看来,Apache Commons 必须是一个“公共基础依赖”才能添加到任何 Java 项目中;)
    • @DasariVinodh 也许是时候迁移您的依赖项了?
    • 这个实现确实尝试在内部捕获 valueOf() ,这正是 OP 试图避免的。我不知道这是一个如此受欢迎的答案。
    • @рüффп OP 明确表示“但我被告知捕获运行时异常不是好的做法”该实现正是这样做的。另外,除非您调整 JVM 本身,否则无法“优化”异常的捕获。所以这仍然不是正确的答案。
    【解决方案4】:

    我不知道为什么有人告诉你捕获运行时异常很糟糕。

    使用valueOf 并捕获IllegalArgumentException 可以很好地将字符串转换/检查为枚举。

    【讨论】:

    • 不,不是,IMO。这是通过异常测试非异常情况——在正常、非错误条件下使用它们进行流量控制。这是对异常 IMO 的一种非常糟糕的使用,并且可能会对性能产生重大影响。异常在性能方面通常很好,因为它们不应该发生 - 但是当您将它们用于非错误条件时,看起来应该快速运行的代码可能会陷入困境。跨度>
    • 有趣的事情 - 基于这个观点(我通常也会分享),我决定使用 Apache Commons EnumUtils.isValidEnum 方式让它尽可能“干净”。猜猜EnumUtils.isValidEnum 是如何实现的——当然是捕捉IllegalArgumentException :-)
    • 好吧,如果您要测试的字符串是由用户提供的,那么获得 IllegalArgumentException 是预期的情况,对吧?老实说,我有点惊讶 Enum 中没有用于此用例的方法。
    • @amdev:非常容易检测到这一点并在自己的代码中抛出异常,如果这是你想要的。当值不存在时,other 用例不得不吞下异常,这要丑得多。如果密钥不存在,您是否相信map.get 也应该抛出异常?有两种不同的方法可能会更干净 - 一种是 throwing ,一种是 not - 但是虽然只有一种方法,但我认为 not throw 会更干净。
    • 当然例外是例外,在我的场景中,应用程序发送order status,理论上枚举将始终匹配字符串。一切都取决于用例,如果你从来没有除了枚举将不匹配字符串,所以它是异常的,它应该引发异常。如果这是一种常见情况,是的,您可能应该在尝试转换之前测试您的字符串是否正确,无论如何在这种情况下,如果您在调用 valueOf 之前测试您的字符串,枚举将不匹配仍然非常例外。
    【解决方案5】:

    根据 Jon Skeet 的回答,我制作了一个允许在工作中轻松完成的课程:

    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Maps;
    
    import java.util.EnumSet;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * <p>
     * This permits to easily implement a failsafe implementation of the enums's valueOf
     * Better use it inside the enum so that only one of this object instance exist for each enum...
     * (a cache could solve this if needed)
     * </p>
     *
     * <p>
     * Basic usage exemple on an enum class called MyEnum:
     *
     *   private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
     *   public static MyEnum failSafeValueOf(String enumName) {
     *       return FAIL_SAFE.valueOf(enumName);
     *   }
     *
     * </p>
     *
     * <p>
     * You can also use it outside of the enum this way:
     *   FailSafeValueOf.create(MyEnum.class).valueOf("EnumName");
     * </p>
     *
     * @author Sebastien Lorber <i>(lorber.sebastien@gmail.com)</i>
     */
    public class FailSafeValueOf<T extends Enum<T>> {
    
        private final Map<String,T> nameToEnumMap;
    
        private FailSafeValueOf(Class<T> enumClass) {
            Map<String,T> map = Maps.newHashMap();
            for ( T value : EnumSet.allOf(enumClass)) {
                map.put( value.name() , value);
            }
            nameToEnumMap = ImmutableMap.copyOf(map);
        }
    
        /**
         * Returns the value of the given enum element
         * If the 
         * @param enumName
         * @return
         */
        public T valueOf(String enumName) {
            return nameToEnumMap.get(enumName);
        }
    
        public static <U extends Enum<U>> FailSafeValueOf<U> create(Class<U> enumClass) {
            return new FailSafeValueOf<U>(enumClass);
        }
    
    }
    

    还有单元测试:

    import org.testng.annotations.Test;
    
    import static org.testng.Assert.*;
    
    
    /**
     * @author Sebastien Lorber <i>(lorber.sebastien@gmail.com)</i>
     */
    public class FailSafeValueOfTest {
    
        private enum MyEnum {
            TOTO,
            TATA,
            ;
    
            private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
            public static MyEnum failSafeValueOf(String enumName) {
                return FAIL_SAFE.valueOf(enumName);
            }
        }
    
        @Test
        public void testInEnum() {
            assertNotNull( MyEnum.failSafeValueOf("TOTO") );
            assertNotNull( MyEnum.failSafeValueOf("TATA") );
            assertNull( MyEnum.failSafeValueOf("TITI") );
        }
    
        @Test
        public void testInApp() {
            assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TOTO") );
            assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TATA") );
            assertNull( FailSafeValueOf.create(MyEnum.class).valueOf("TITI") );
        }
    
    }
    

    请注意,我使用 Guava 制作了 ImmutableMap 但实际上您可以使用法线贴图,我认为因为该贴图永远不会返回...

    【讨论】:

      【解决方案6】:

      大多数答案建议要么使用带有等于的循环来检查枚举是否存在,要么使用带有 enum.valueOf() 的 try/catch。我想知道哪种方法更快并尝试过。我不是很擅长基准测试,所以如果我犯了任何错误,请纠正我。

      这是我的主类的代码:

          package enumtest;
      
      public class TestMain {
      
          static long timeCatch, timeIterate;
          static String checkFor;
          static int corrects;
      
          public static void main(String[] args) {
              timeCatch = 0;
              timeIterate = 0;
              TestingEnum[] enumVals = TestingEnum.values();
              String[] testingStrings = new String[enumVals.length * 5];
              for (int j = 0; j < 10000; j++) {
                  for (int i = 0; i < testingStrings.length; i++) {
                      if (i % 5 == 0) {
                          testingStrings[i] = enumVals[i / 5].toString();
                      } else {
                          testingStrings[i] = "DOES_NOT_EXIST" + i;
                      }
                  }
      
                  for (String s : testingStrings) {
                      checkFor = s;
                      if (tryCatch()) {
                          ++corrects;
                      }
                      if (iterate()) {
                          ++corrects;
                      }
                  }
              }
      
              System.out.println(timeCatch / 1000 + "us for try catch");
              System.out.println(timeIterate / 1000 + "us for iterate");
              System.out.println(corrects);
          }
      
          static boolean tryCatch() {
              long timeStart, timeEnd;
              timeStart = System.nanoTime();
              try {
                  TestingEnum.valueOf(checkFor);
                  return true;
              } catch (IllegalArgumentException e) {
                  return false;
              } finally {
                  timeEnd = System.nanoTime();
                  timeCatch += timeEnd - timeStart;
              }
      
          }
      
          static boolean iterate() {
              long timeStart, timeEnd;
              timeStart = System.nanoTime();
              TestingEnum[] values = TestingEnum.values();
              for (TestingEnum v : values) {
                  if (v.toString().equals(checkFor)) {
                      timeEnd = System.nanoTime();
                      timeIterate += timeEnd - timeStart;
                      return true;
                  }
              }
              timeEnd = System.nanoTime();
              timeIterate += timeEnd - timeStart;
              return false;
          }
      }
      

      这意味着,每个方法运行 50000 倍于枚举的长度 我多次运行此测试,使用 10、20、50 和 100 个枚举常量。 结果如下:

      • 10:尝试/捕获:760 毫秒 |迭代:62ms
      • 20:尝试/捕获:1671 毫秒 |迭代:177ms
      • 50:尝试/捕获:3113 毫秒 |迭代:488ms
      • 100:尝试/捕获:6834 毫秒 |迭代:1760ms

      这些结果并不准确。再次执行时,结果有高达 10% 的差异,但它们足以表明 try/catch 方法的效率要低得多,尤其是在使用小的枚举时。

      【讨论】:

        【解决方案7】:

        从 Java 8 开始,我们可以使用 streams 代替 for 循环。此外,如果枚举没有具有此类名称的实例,则返回 Optional 可能是合适的。

        我想出了以下三个关于如何查找枚举的替代方法:

        private enum Test {
            TEST1, TEST2;
        
            public Test fromNameOrThrowException(String name) {
                return Arrays.stream(values())
                        .filter(e -> e.name().equals(name))
                        .findFirst()
                        .orElseThrow(() -> new IllegalArgumentException("No enum with name " + name));
            }
        
            public Test fromNameOrNull(String name) {
                return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst().orElse(null);
            }
        
            public Optional<Test> fromName(String name) {
                return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst();
            }
        }
        

        【讨论】:

          【解决方案8】:

          只需使用valueOf() 方法。

          如果该值不存在,则抛出IllegalArgumentException,您可以像这样catch

          boolean isSettingCodeValid = true;
          try {
              SettingCode.valueOf(settingCode.toUpperCase());
          } catch (IllegalArgumentException e) {
              // throw custom exception or change the isSettingCodeValid value
              isSettingCodeValid = false;
          }
          

          【讨论】:

            【解决方案9】:

            您也可以使用 Guava 并执行以下操作:

            // This method returns enum for a given string if it exists, otherwise it returns default enum.
            private MyEnum getMyEnum(String enumName) {
              // It is better to return default instance of enum instead of null
              return hasMyEnum(enumName) ? MyEnum.valueOf(enumName) : MyEnum.DEFAULT;
            }
            
            // This method checks that enum for a given string exists.
            private boolean hasMyEnum(String enumName) {
              return Iterables.any(Arrays.asList(MyEnum.values()), new Predicate<MyEnum>() {
                public boolean apply(MyEnum myEnum) {
                  return myEnum.name().equals(enumName);
                }
              }); 
            }
            

            在第二种方法中,我使用 guava (Google Guava) 库,它提供了非常有用的 Iterables 类。使用 Iterables.any() 方法,我们可以检查给定值是否存在于列表对象中。此方法需要两个参数:一个列表和 Predicate 对象。首先,我使用 Arrays.asList() 方法创建一个包含所有枚举的列表。之后,我创建了新的 Predicate 对象,用于检查给定元素(在我们的例子中是枚举)是否满足 apply 方法中的条件。如果发生这种情况,方法 Iterables.any() 返回真值。

            【讨论】:

              【解决方案10】:

              这是我用来检查具有给定名称的枚举常量是否存在的方法:

              java.util.Arrays.stream(E.values()).map(E::name).toList().contains("");
              

              (假设您的枚举名为 E。)在“”中,您应该输入一个枚举常量的名称,您希望检查它是否在枚举中定义。 这当然不是最好的解决方案,因为它将数组转换为 Stream,然后再转换为 List,但它又好又短,对我来说效果很好。

              正如其他人所提到的,自从您在 2009 年提出这个问题以来,自 2009 年以来,这在您的情况下将不起作用(除非您迁移到更新版本的 Java)。Java 不支持此答案中使用的功能。但无论如何我都会发布,以防有​​人使用较新版本的 Java 想要这样做。

              【讨论】:

                猜你喜欢
                • 2021-05-12
                • 1970-01-01
                • 2015-02-12
                • 2017-10-03
                • 1970-01-01
                • 2013-03-12
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多