查找 Java Enum 的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2418729/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 07:21:18  来源:igfitidea点击:

Best practice to look up Java Enum

javaenums

提问by Marcus Leon

We have a REST API where clients can supply parameters representing values defined on the server in Java Enums.

我们有一个 REST API,客户端可以在其中提供表示在 Java Enums 中定义在服务器上的值的参数。

So we can provide a descriptive error, we add this lookupmethod to each Enum. Seems like we're just copying code (bad). Is there a better practice?

所以我们可以提供一个描述性错误,我们将这个lookup方法添加到每个 Enum 中。似乎我们只是在复制代码(不好)。有没有更好的做法?

public enum MyEnum {
    A, B, C, D;

    public static MyEnum lookup(String id) {
        try {
            return MyEnum.valueOf(id);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Invalid value for my enum blah blah: " + id);
        }
    }
}

Update: The default error message provided by valueOf(..)would be No enum const class a.b.c.MyEnum.BadValue. I would like to provide a more descriptive error from the API.

更新:提供的默认错误消息valueOf(..)No enum const class a.b.c.MyEnum.BadValue. 我想从 API 中提供更具描述性的错误。

采纳答案by Mykola Golubyev

Probably you can implement generic static lookupmethod.

也许您可以实现通用静态lookup方法。

Like so

像这样

public class LookupUtil {
   public static <E extends Enum<E>> E lookup(Class<E> e, String id) {   
      try {          
         E result = Enum.valueOf(e, id);
      } catch (IllegalArgumentException e) {
         // log error or something here

         throw new RuntimeException(
           "Invalid value for enum " + e.getSimpleName() + ": " + id);
      }

      return result;
   }
}

Then you can

然后你可以

public enum MyEnum {
   static public MyEnum lookup(String id) {
       return LookupUtil.lookup(MyEnum.class, id);
   }
}

or call explicitly utility class lookup method.

或显式调用实用程序类查找方法。

回答by Kannan Ekanath

Why do we have to write that 5 line code ?

为什么我们要写那 5 行代码?

public class EnumTest {
public enum MyEnum {
    A, B, C, D;
}

@Test
public void test() throws Exception {
    MyEnum.valueOf("A"); //gives you A
    //this throws ILlegalargument without having to do any lookup
    MyEnum.valueOf("RADD"); 
}
}

回答by Adam

If you want the lookup to be case insensitive you can loop through the values making it a little more friendly:

如果您希望查找不区分大小写,您可以遍历值使其更友好:

 public enum MyEnum {
   A, B, C, D;

      public static MyEnum lookup(String id) {
        boolean found = false;
        for(MyEnum enum: values()){
           if(enum.toString().equalsIgnoreCase(id)) found = true;
        }  
        if(!found) throw new RuntimeException("Invalid value for my enum: " +id);
       }
}

回答by Vincent Robert

Looks like you have a bad practice here but not where you think.

看起来你在这里有一个不好的做法,但不是你认为的地方。

Catching an IllegalArgumentExceptionto rethrow another RuntimeExceptionwith a clearer message might look like a good idea but it is not. Because it means you care about messages in your exceptions.

抓住一个IllegalArgumentExceptionRuntimeException更清晰的消息重新抛出另一个可能看起来是个好主意,但事实并非如此。因为这意味着您关心异常中的消息。

If you care about messages in your exceptions, then it means that your user is somehow seeing your exceptions. This is bad.

如果您关心异常中的消息,则意味着您的用户以某种方式看到了您的异常。这不好。

If you want to provide an explicit error message to your user, you should check the validity of the enum value when parsing user input and send the appropriate error message in the response if user input is incorrect.

如果要向用户提供明确的错误消息,则应在解析用户输入时检查枚举值的有效性,并在用户输入不正确时在响应中发送相应的错误消息。

Something like:

就像是:

// This code uses pure fantasy, you are warned!
class MyApi
{
    // Return the 24-hour from a 12-hour and AM/PM

    void getHour24(Request request, Response response)
    {
        // validate user input
        int nTime12 = 1;
        try
        {
            nTime12 = Integer.parseInt(request.getParam("hour12"));
            if( nTime12 <= 0 || nTime12 > 12 )
            {
                throw new NumberFormatException();
            }
        }
        catch( NumberFormatException e )
        {
            response.setCode(400); // Bad request
            response.setContent("time12 must be an integer between 1 and 12");
            return;
        }

        AMPM pm = null;
        try
        {
            pm = AMPM.lookup(request.getParam("pm"));
        }
        catch( IllegalArgumentException e )
        {
            response.setCode(400); // Bad request
            response.setContent("pm must be one of " + AMPM.values());
            return;
        }

        response.setCode(200);
        switch( pm )
        {
            case AM:
                response.setContent(nTime12);
                break;
            case PM:
                response.setContent(nTime12 + 12);
                break;
        }
        return;
    }
}

回答by Robin

The error message in IllegalArgumentException is already descriptive enough.

IllegalArgumentException 中的错误消息已经具有足够的描述性。

Your method makes a generic exception out of a specific one with the same message simply reworded. A developer would prefer the specific exception type and can handle the case appropriately instead of trying to handle RuntimeException.

您的方法从特定的异常中创建了一个通用异常,并简单地改写了相同的消息。开发人员更喜欢特定的异常类型,并且可以适当地处理这种情况,而不是尝试处理 RuntimeException。

If the intent is to make the message more user friendly, then references to values of enums is irrelevant to them anyway. Let the UI code determine what should be displayed to the user, and the UI developer would be better off with the IllegalArgumentException.

如果目的是使消息对用户更友好,那么对枚举值的引用无论如何都与它们无关。让 UI 代码决定应该向用户显示什么,UI 开发人员最好使用 IllegalArgumentException。

回答by makrom

update: As GreenTurtle correctly remarked, the following is wrong

更新:正如 GreenTurtle 正确评论的那样,以下是错误的



I would just write

我只想写

boolean result = Arrays.asList(FooEnum.values()).contains("Foo");

This is possibly less performant than catching a runtime exception, but makes for much cleaner code. Catching such exceptions is always a bad idea, since it is prone to misdiagnosis. What happens when the retrieval of the compared value itself causes an IllegalArgumentException ? This would then be treaten like a non matching value for the enumerator.

这可能比捕获运行时异常性能低,但可以使代码更清晰。捕捉这样的异常总是一个坏主意,因为它很容易被误诊。当检索比较值本身导致 IllegalArgumentException 时会发生什么?这将被视为枚举器的非匹配值。

回答by Radu Sebastian LAZIN

We do all our enums like this when it comes to Rest/Json etc. It has the advantage that the error is human readable and also gives you the accepted value list. We are using a custom method MyEnum.fromString instead of MyEnum.valueOf, hope it helps.

当涉及到 Rest/Json 等时,我们像这样执行所有枚举。 它的优点是错误是人类可读的,并且还为您提供了可接受的值列表。我们正在使用自定义方法 MyEnum.fromString 而不是 MyEnum.valueOf,希望它有所帮助。

public enum MyEnum {

    A, B, C, D;

    private static final Map<String, MyEnum> NAME_MAP = Stream.of(values())
            .collect(Collectors.toMap(MyEnum::toString, Function.identity()));

    public static MyEnum fromString(final String name) {
        MyEnum myEnum = NAME_MAP.get(name);
        if (null == myEnum) {
            throw new IllegalArgumentException(String.format("'%s' has no corresponding value. Accepted values: %s", name, Arrays.asList(values())));
        }
        return myEnum;
    }
}

so for example if you call

所以例如如果你打电话

MyEnum value = MyEnum.fromString("X");

you'll get an IllegalArgumentException with the following message:

您将收到带有以下消息的 IllegalArgumentException:

'X' has no corresponding value. Accepted values: [A, B, C, D]

'X' 没有对应的值。接受值:[A、B、C、D]

you can change the IllegalArgumentException to a custom one.

您可以将 IllegalArgumentException 更改为自定义异常。

回答by Ken Chan

Guava also provides such function which will return an Optionalif an enum cannot be found.

Guava 还提供了这样的函数,Optional如果找不到枚举,它将返回一个。

Enums.getIfPresent(MyEnum.class, id).toJavaUtil()
            .orElseThrow(()-> new RuntimeException("Invalid enum blah blah blah.....")))

回答by mskfisher

You can use a static lookup map to avoid the exception and return a null, then throw as you'd like:

您可以使用静态查找映射来避免异常并返回空值,然后根据需要抛出:

public enum Mammal {
    COW,
    MOUSE,
    OPOSSUM;

    private static Map<String, Mammal> lookup = 
            Arrays.stream(values())
                  .collect(Collectors.toMap(Enum::name, Function.identity()));

    public static Mammal getByName(String name) {
        return lookup.get(name);
    }
}

回答by Crozeta

Apache Commons Lang 3 contais the class EnumUtils. If you aren't using Apache Commons in your projects, you're doing it wrong. You are reinventing the wheel!

Apache Commons Lang 3 包含类 EnumUtils。如果您没有在项目中使用 Apache Commons,那么您就做错了。你在重新发明轮子!

There's a dozen of cool methods that we could use without throws an Exception. For example:

我们可以使用十几种很酷的方法而不会抛出异常。例如:

Gets the enum for the class, returning null if not found.

This method differs from Enum.valueOf in that it does not throw an exceptionfor an invalid enum name and performs case insensitive matching of the name.

获取类的枚举,如果未找到则返回 null。

此方法与 Enum.valueOf 的不同之处在于它不会为无效的枚举名称引发异常,并且执行名称的大小写不敏感匹配。

EnumUtils.getEnumIgnoreCase(SeasonEnum.class, season);