查找 Java Enum 的最佳实践

发布于 2024-08-24 19:15:24 字数 593 浏览 3 评论 0原文

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

因此我们可以提供一个描述性错误,我们将这个 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);
        }
    }
}

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

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

羞稚 2024-08-31 19:15:24

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

像这样

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;
   }
}

然后你可以

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

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

Probably you can implement generic static lookup method.

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.

ゃ人海孤独症 2024-08-31 19:15:24

看起来你在这里的做法很糟糕,但不是你想象的那样。

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

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

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

像这样的东西:

// 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;
    }
}

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

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

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;
    }
}
星星的軌跡 2024-08-31 19:15:24

当涉及到 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;
    }
}

例如,如果您调用,

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

您将收到一个 IllegalArgumentException 并显示以下消息:

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

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

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.

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:

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

you can change the IllegalArgumentException to a custom one.

Bonjour°[大白 2024-08-31 19:15:24

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

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

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

Enums.getIfPresent(MyEnum.class, id).toJavaUtil()
            .orElseThrow(()-> new RuntimeException("Invalid enum blah blah blah.....")))
作死小能手 2024-08-31 19:15:24

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

我们可以使用许多很酷的方法而不会引发异常。例如:

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

此方法与 Enum.valueOf 的不同之处在于它不会抛出异常
无效枚举名称的异常并执行不区分大小写的操作
名称匹配。

EnumUtils.getEnumIgnoreCase(SeasonEnum.class, season);

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!

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.

EnumUtils.getEnumIgnoreCase(SeasonEnum.class, season);
能怎样 2024-08-31 19:15:24

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

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

      public static MyEnum lookup(final String id) {
        for(MyEnum enumValue: values()){
           if(enumValue.name().equalsIgnoreCase(id)){
              return enumValue;
           }
        }  
        throw new RuntimeException("Invalid value for my enum: " + id);
       }
}

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(final String id) {
        for(MyEnum enumValue: values()){
           if(enumValue.name().equalsIgnoreCase(id)){
              return enumValue;
           }
        }  
        throw new RuntimeException("Invalid value for my enum: " + id);
       }
}
苏璃陌 2024-08-31 19:15:24

为什么我们必须写那 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"); 
}
}

Why do we have to write that 5 line code ?

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"); 
}
}
对你的占有欲 2024-08-31 19:15:24

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

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

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

The error message in IllegalArgumentException is already descriptive enough.

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.

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.

‖放下 2024-08-31 19:15:24

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


我只会写

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

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

update: As GreenTurtle correctly remarked, the following is wrong


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.

不气馁 2024-08-31 19:15:24

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

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);
    }
}

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文