2010-03-10 87 views
31

我们有一个REST API,其中客户端可以提供表示Java Enums中服务器上定义的值的参数。查找Java枚举的最佳实践

所以我们可以提供一个描述性错误,我们将这个lookup方法添加到每个枚举。好像我们只是复制代码(坏)。有更好的做法吗?

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 a.b.c.MyEnum.BadValue。我想从API中提供更多的描述性错误。

+2

好吧,IllegalArgumentException没有错,它已经是一个RuntimeException。你想在这里改进什么? – Riduidel 2010-03-10 16:50:39

+0

添加比仅使用'valueOf'(和'IllegalArgumentException')时提供的描述性消息更多的描述性消息 – 2010-03-10 16:51:47

回答

26

也许你可以实现通用静态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); 
    } 
} 

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

+0

您不能创建基类枚举来扩展子枚举,因为“所有枚举隐式扩展java.lang.Enum。不支持多重继承,枚举不能扩展其他任何东西“,所以无处可放这个代码,除非你想把它放在某种静态工具类中,并用enum类参数调用它。 – 2010-03-10 16:55:45

+0

只是调用Enum.valueOf,你的实用方法有什么优势? – sleske 2010-03-10 17:15:36

+0

发布和预先操作和错误处理。据我所知它是错误处理,它是复制和粘贴不同的枚举类型。 – 2010-03-10 17:18:07

3

为什么我们要写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"); 
} 
} 
+0

这个想法/要求是重新抛出特殊的例外 – 2010-03-10 17:28:26

+0

没错。有关“默认”错误与更多描述性错误的更多描述,请参阅更新后的文章。 – 2010-03-17 15:49:01

+0

在这种情况下,来自@“Mykola Golubyev”的答案应该已经解决了您的静态查找实用程序类的问题,并且您可以在catch块中抛出任何想要的消息/异常? – 2010-03-17 16:30:57

3

如果您想查询是不区分大小写的,你可以通过做多一点友好的数值循环:

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

好的建议,不是我真正想要的,而是一个好主意。注意在你的'lookup'中,你实际上并没有从方法中返回任何值。 – 2010-03-10 18:07:37

+0

好点。应该是: if(enum.toString()。equalsIgnoreCase(id))return enum; 然后,您可以消除布尔值,只要抛出异常就会抛出异常。 – Adam 2010-03-10 20:58:55

11

看起来你在这里有一个不好的做法,但不是你想的。

抓住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; 
    } 
} 
+0

我认为我们实际上是按照你的建议去做的。我们抛出异常,然后栈上的一个类捕获它,然后通过REST API返回异常消息给客户端(在定义的错误响应中) – 2010-03-10 18:06:01

+5

我建议你不要捕获泛型异常以返回它们消息,但返回该消息意在解释为什么异常被捕获。根据您的例外情况进行通用错误响应是内部架构泄露给客户端,这是一种不好的做法。您的API的错误消息不应该依赖于实现的错误报告体系结构(在这种情况下,Java异常)。 – 2010-03-10 21:20:44

2

IllegalArgumentException异常的错误消息是已经有足够的描述。

你的方法使用相同的消息只是重写了一个特定的异常。开发人员更喜欢特定的异常类型,并且可以适当地处理这种情况,而不是尝试处理RuntimeException。

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

+0

“默认”错误消息是:'没有枚举常量类a.b.c.MyEnum.BadValue'。我宁愿从REST API返回更相关的错误消息。创建前端的开发人员不会真正知道如何处理默认错误,而不是仅仅显示它 - 这是我想避免的。 – 2010-03-17 15:46:11

+2

我无法看到您的示例消息如何更好。对于用户来说都不合适,所以应该处理异常并且显示正确的面向用户的消息。这是不同的,然后将消息放入异常中,这应该是开发者的。 – Robin 2010-03-17 18:45:27

3

更新:由于GreenTurtle正确地指出,下列哪项是错误


我只想写

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

这可能是比捕捉运行时异常少高性能,反而使得对于更清洁代码。捕捉这样的例外总是一个坏主意,因为它很容易误诊。 当检索比较值本身会导致IllegalArgumentException时会发生什么?然后这将被处理为与枚举器不匹配的值。

+6

除非在FooEnum类中实现了一个可以处理String对象的equals方法,否则此代码将永远不会返回true。发生这种情况是因为生成的ArrayList包含FooEnum类型的对象,并且如果使用String对象请求包含,将始终返回false。 – GreenTurtle 2015-03-27 13:56:49

+0

@GreenTurtle是的,你显然是对的,忘记了Enum#只能用于对象比较。对于枚举,你甚至不应该能够实现你自己的平等。 我仍然会尽量避免捕获泛型运行时异常,只是遍历它,做一个字符串比较。 – makrom 2015-10-24 17:03:49

0

当涉及到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更改为自定义一个。