Лучшая практика для поиска Java Enum - PullRequest
42 голосов
/ 10 марта 2010

У нас есть REST API, где клиенты могут предоставлять параметры, представляющие значения, определенные на сервере в Java Enums.

Таким образом, мы можем предоставить описательную ошибку, мы добавляем этот 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 a.b.c.MyEnum.BadValue. Я хотел бы предоставить более описательную ошибку от API.

Ответы [ 8 ]

33 голосов
/ 10 марта 2010

Возможно, вы сможете реализовать общий статический метод 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);
   }
}

или вызовите явно метод поиска класса утилит.

13 голосов
/ 10 марта 2010

Похоже, у вас здесь плохая практика, но не там, где вы думаете.

Поймать IllegalArgumentException для отбрасывания другого RuntimeException с более ясным сообщением может показаться хорошей идеей, но это не так. Потому что это означает, что вы заботитесь о сообщениях в своих исключениях.

Если вы заботитесь о сообщениях в своих исключениях, то это означает, что ваш пользователь каким-то образом видит ваши исключения. Это плохо.

Если вы хотите предоставить явное сообщение об ошибке своему пользователю, вы должны проверить правильность значения enum при анализе ввода пользователя и отправить соответствующее сообщение об ошибке в ответе, если ввод пользователя неверен.

Что-то вроде:

// 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;
    }
}
3 голосов
/ 10 марта 2010

Если вы хотите, чтобы поиск не учитывал регистр, вы можете перебирать значения, делая его немного более удобным:

 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);
       }
}
3 голосов
/ 10 марта 2010

Почему мы должны написать этот 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"); 
}
}
2 голосов
/ 14 февраля 2018

Мы делаем все наши перечисления, как это, когда дело доходит до 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 со следующим сообщением:

«Х» не имеет соответствующего значения. Допустимые значения: [A, B, C, D]

вы можете изменить IllegalArgumentException на пользовательский.

2 голосов
/ 01 апреля 2014

обновление: как правильно заметил GreenTurtle, следующее неверно


Я бы просто написал

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

Это, возможно, менее производительно, чем перехват исключения во время выполнения, но делает код намного чище. Поймать такие исключения всегда плохая идея, так как она склонна к ошибочному диагнозу. Что происходит, когда сам поиск сравниваемого значения вызывает исключение IllegalArgumentException? Это будет обработано как несоответствующее значение для перечислителя.

2 голосов
/ 10 марта 2010

Сообщение об ошибке в IllegalArgumentException уже достаточно наглядно.

Ваш метод делает общее исключение из конкретного с тем же сообщением, просто перефразированным. Разработчик предпочел бы конкретный тип исключения и может обработать случай соответствующим образом, вместо того, чтобы пытаться обработать RuntimeException.

Если целью является сделать сообщение более удобным для пользователя, то ссылки на значения перечислений для них все равно не имеют значения. Позвольте коду пользовательского интерфейса определить, что должно отображаться для пользователя, и разработчику пользовательского интерфейса будет лучше с IllegalArgumentException.

1 голос
/ 06 декабря 2018

Guava также предоставляет такую ​​функцию, которая будет возвращать Optional, если перечисление не может быть найдено.

Enums.getIfPresent(MyEnum.class, id).toJavaUtil()
            .orElseThrow(()-> new RuntimeException("Invalid enum blah blah blah.....")))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...