Подтвердить что ты не робот

Лучшая практика поиска Java Enum

У нас есть 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.

4b9b3361

Ответ 1

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

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

Ответ 2

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

Захват 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;
    }
}

Ответ 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"); 
}
}

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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


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

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

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

Ответ 7

Мы делаем все наши перечисления, как это, когда речь заходит о 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 на пользовательский.

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

Apache Commons Lang 3 относится к классу EnumUtils. Если вы не используете Apache Commons в своих проектах, вы делаете это неправильно. Вы заново изобретаете колесо!

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

Получает перечисление для класса, возвращая ноль, если не найден.

Этот метод отличается от Enum.valueOf тем, что он не выдает исключение для недопустимого имени перечисления и выполняется без учета регистра сопоставление имени.

EnumUtils.getEnumIgnoreCase(SeasonEnum.class, season);