查找java枚举的最佳实践

fnvucqvd  于 2021-08-20  发布在  Java
关注(0)|答案(10)|浏览(344)

我们有一个RESTAPI,客户机可以在其中提供表示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中提供一个更具描述性的错误。

lymgl2op

lymgl2op1#

您可以使用静态查找Map来避免异常并返回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);
    }
}
wpcxdonn

wpcxdonn2#

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

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

yruzcnhs

yruzcnhs3#

看起来你在这里练习得不好,但不是在你想的地方。
抓住一个 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;
    }
}
e1xvtsh3

e1xvtsh34#

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

atmip9wb

atmip9wb5#

Guava也提供了这样的功能,它将返回 Optional 如果找不到枚举。

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

xe55xuns6#

如果希望查找不区分大小写,可以通过值进行循环,使其更友好:

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

bvuwiixz7#

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

dkqlctbz8#

illegalargumentexception中的错误消息已经具有足够的描述性。
您的方法对一个特定的异常生成一个通用异常,并简单地重写相同的消息。开发人员更喜欢特定的异常类型,并且可以适当地处理该情况,而不是试图处理runtimeexception。
如果目的是使消息更加用户友好,那么对枚举值的引用与它们无关。让ui代码决定应该向用户显示什么,ui开发人员最好使用illegalargumentexception。

hyrbngr7

hyrbngr79#

更新:正如绿海龟正确评论的那样,以下是错误的
我只会写信

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

这可能比捕获运行时异常的性能要差,但会使代码更干净。捕捉这样的异常总是一个坏主意,因为它容易被误诊。当检索比较值本身导致illegalargumentexception时会发生什么情况?然后将其视为枚举数的不匹配值。

ecfdbz9o

ecfdbz9o10#

apache commons lang 3包含类enumutils。如果您在项目中没有使用ApacheCommons,那么您就错了。你在重新发明轮子!
我们可以毫无例外地使用十几种很酷的方法。例如:
获取类的枚举,如果找不到,则返回null。
此方法与enum.valueof的不同之处在于,它不会为无效的enum名称引发异常,并对名称执行不区分大小写的匹配。

EnumUtils.getEnumIgnoreCase(SeasonEnum.class, season);

相关问题