Получение перечисления с использованием 'valueOf' throws RuntimeException - что использовать вместо этого?

У меня есть следующее перечисление

enum Animal implements Mammal {
   CAT, DOG;

   public static Mammal findMammal(final String type) {
      for (Animal a : Animal.values()) {
         if (a.name().equals(type)) {
            return a;
         }
      }
   }
}

Я изначально использовал Enum.valueOf(Animal.class, "DOG"); для поиска конкретного Animal. Тем не менее, я не знал, что если совпадение не найдено, бросается IllegalArgumentException. Я думал, что, возможно, нуль был возвращен. Так что это дает мне проблему. Я не хочу ловить этот IllegalArgumentException, если совпадение не найдено. Я хочу, чтобы иметь возможность искать все перечисления типа Mammal, и я не хочу реализовывать этот статический "findMammal" для каждого перечисления типа Mammal. Поэтому мой вопрос заключается в том, что было бы самым благоприятным дизайнерским решением для реализации этого поведения? У меня будет код вызова следующим образом:

public class Foo {
   public Mammal bar(final String arg) {
      Mammal m = null;
      if (arg.equals("SomeValue")) {
         m = Animal.findMammal("CAT");
      } else if (arg.equals("AnotherValue") {
         m = Human.findMammal("BILL");
      }
      // ... etc
   }
}

Как вы можете видеть, у меня есть разные типы млекопитающих - "Животное", "Человек", которые перечислены. Я не хочу использовать "findMammal" для каждого перечисления млекопитающих. Я полагаю, лучший выбор - просто создать класс утилиты, который принимает аргумент Mammal и ищет его? Возможно, есть более аккуратное решение.

Ответ 1

Как насчет создания HashMap<String, Mammal>? Вам нужно сделать это только один раз...

public class Foo {

  private static final Map<String, Mammal> NAME_TO_MAMMAL_MAP;

  static {
    NAME_TO_MAMMAL_MAP = new HashMap<String, Mammal>();
    for (Human human : EnumSet.allOf(Human.class)) {
      NAME_TO_MAMMAL_MAP.put(human.name(), human);
    }
    for (Animal animal : EnumSet.allOf(Animal.class)) {
      NAME_TO_MAMMAL_MAP.put(animal.name(), animal);
    }
  }

  public static Mammal bar(final String arg) {
    return NAME_TO_MAMMAL_MAP.get(arg);
  }
}

Примечания:

  • Это вернет null, если имя не существует
  • Это не обнаружит столкновения имен
  • Вы можете использовать неизменяемую карту некоторого описания (например, через Guava)
  • Возможно, вы захотите написать метод утилиты для создания неизменяемой карты имени к значению для общего перечисления, а затем просто слить карты:)

Ответ 2

Если вы не хотите касаться каждого Enum (понятного), метод утилиты - это путь. Я понимаю, что я не хочу поймать исключение в вашем коде, поймать его в методе должно быть хорошо, хотя:

public static <T extends Enum<T>> T findEnumValue(Class<T> type, String name) {
    if (name == null)
        return null;
    try {
        return Enum.valueOf(type, name.toUpperCase());
    } catch (IllegalArgumentException iae) {
        return null;
    }
}

Ответ 3

Вы можете использовать библиотеку guava. Enums класс позволяет это:

Enums.valueOfFunction(MyEnum.class).apply(id);

Ответ 4

Вы можете использовать getEnum Метод vom apache EnumUtils. Он возвращает перечисление или null, если не найден.

EnumUtils.getEnum(Animal.class, "CAT");

Ответ 5

У вас есть ошибка в коде. Если вам нужна ваша функция для возврата null, когда она не находит что-то, просто верните ее:

enum Animal implements Mammal {
   CAT, DOG;

   public static Mammal findMammal(final String type) {
      for (Animal a : Animal.values()) {
         if (a.name().equals(type)) {
            return a;
         }
      }
      return null; // this line works if nothing is found.
   }
}

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

Ваш выбор: Enum - EnumMaps - HashMap зависит от динамики ваших данных. Константы континуума не могут быть созданы во время выполнения. С другой стороны, вам не нужно проверять многие вещи во время выполнения - компилятор сделает это. И вы наверняка забудете что-то проверить. То, что управляет Джон Скит, может дать вам головную боль.

Ответ 6

Я не задаю вопрос, но мне также нужен valueOf, который возвращает null вместо того, чтобы бросать исключение, поэтому я создал этот метод утилиты:

public static <T extends Enum<T>> T valueOf(T[] values, String name)
{
    for (T value : values) {
        if (value.name().equals(name)) {
            return value;
        }
    }
    return null;
}

Вы можете называть его следующим образом:

Animal a = valueOf(Animal.values(), "CAT");

Ответ 7

Если вы можете сказать

m = Animal.findMammal("CAT");

Почему вы не можете сказать

m = Animal.CAT;

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

UPDATE (возможно, более подробно)

Это немного сложнее, но...

public interface Mammal{

    public void doMammalStuff();

    public static interface Finder{
        public Mammal find(String name, Class<Enum<?>> enumType);
    }

    public static Finder FINDER = new Finder(){
        public Mammal find(String name, Class<Enum<?>> enumType) {
            return enumType.forName( name );
    };
    };
}

Затем вы можете позвонить

Mammal m = Mammal.FINDER.find( "CAT", Animal.class );

Ваша реализация find() может выглядеть по-другому (т.е. не выбрасывать исключение).