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

Проверьте правильные значения перечисления перед использованием перечисления

Я пытаюсь найти набор Enum, зная, что часто возникает несоответствие, которое генерирует исключение: я хотел бы проверить, существует ли значение перед выполнением поиска, чтобы избежать исключений. Мой enum выглядит примерно так:

public enum Fruit {
    APPLE("apple"),
    ORANGE("orange");
    ;
    private final String fruitname;
    Fruit(String fruitname) {
        this.fruitname = fruitname;
    }
    public String fruitname() {return fruitname;}
}

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

Fruit.values()[i].fruitname

но я хотел бы сделать что-то вроде (pseduo-code):

if (Fruit.values().contains(myStringHere)) {...

Это возможно? Должен ли я использовать что-то еще (Массивы? Карты?)?

EDIT: в конце концов я пошел с предложением NawaMan, но спасибо всем за полезный вклад.

4b9b3361

Ответ 1

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

public enum Fruit {
   ...
   static public boolean isMember(String aName) {
       Fruit[] aFruits = Fruit.values();
       for (Fruit aFruit : aFruits)
           if (aFruit.fruitname.equals(aName))
               return true;
       return false;
   }
   ...
}

Ответ 2

Существует apache commons lang EnumUtils.isValidEnum(). К сожалению, под капотом это использует логику try/catch и возвращает логическое значение, но, по крайней мере, ваш код выглядит чистым:

if(EnumUtils.isValidEnum(Fruit.class, fruitname)) { ....

Вам понадобится использовать последнюю библиотеку commons-lang3, так как commons-lang 2.x не имеет этой функции.

Ответ 3

Когда я это делаю, я обычно пересаживаю его в класс enum.

public enum Fruit {
        APPLE("apple"),
        ORANGE("orange");

    // Order of initialisation might need adjusting, I haven't tested it.
    private static final Map<String, Fruit> lookup = new HashMap<String, Fruit>();
    private final String fruitname;
    Fruit(String fruitname) {
        this.fruitname = fruitname;
        lookup.put(fruitname, Fruit);
    }
    public String fruitname() {return fruitname;}

    public static Fruit fromFruitname(String fruitname) {
        return lookup.get(fruitname);
    }
}

Но:

  • Для небольших перечислений, вероятно, более эффективно переходить через список.

Кстати:

  • В этой ситуации я бы пошел с условным обозначением и использовал имя(), так как это то же самое, что и пользовательское имя, кроме случая (легко исправлено).
  • Это решение более полезно, когда то, что вам нужно найти, полностью отличается от значения name().

Ответ 4

Вот как вы можете это сделать, используя EnumSet.allOf для заполнения карты:

public enum Fruit {

    APPLE("apple"), 
    ORANGE("orange");

    private static final Map<String, Fruit> nameToValueMap = new HashMap<String, Fruit>();

    static {
        for (Fruit value : EnumSet.allOf(Fruit.class)) {
            nameToValueMap.put(value.name(), value);
        }
    }

    private final String fruitname;

    Fruit(String fruitname) {
        this.fruitname = fruitname;
    }

    public String fruitname() {
        return fruitname;
    }

    public static Fruit forName(String name) {
        return nameToValueMap.get(name);
    }
}

Ответ 5

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

public enum Fruit{
    Apple, 
    Orange;

    private final static Set<String> values = new HashSet<String>(Fruit.values().length);

    static{
        for(Fruit f: Fruit.values())
            values.add(f.name());
    }

    public static boolean contains( String value ){
        return values.contains(value);
    }

}

Ответ 6

Я буду противником здесь... Я думаю, что ваш первый импульс (чтобы исключить) - это правильная вещь.

Если вы проверяете внутри бизнес-логики, а не пользовательский интерфейс, на этом уровне пользователю не будет обратной связи. (Если вы не проверяете пользовательский интерфейс, у нас есть другие проблемы). Поэтому правильный способ справиться с этим - это выброс исключения.

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

Короче... вы были на деньги с вашей первой мыслью. Смирись с этим. Просто измените обработку исключений немного иначе.

Ответ 7

В java8 вы можете сделать это так:

 public static boolean isValidFruit(final String fruit) {
    return Arrays.stream(Fruit.values())
        .map(Fruit::name)
        .collect(Collectors.toSet())
        .contains(fruit);
}

Ответ 8

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


Я думаю, что вы упомянули for loop, если у вас есть только несколько значений перечисления. Вероятно, он будет иметь лучшую производительность. Но я понимаю, что ты этого не хочешь.


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

Обновление: Trejkaz уже разместил код, который делает это.


Также обратите внимание, что иногда вместо возвращения null в качестве возвращаемого типа, когда экземпляр не совпадает, некоторые перечисления имеют выделенный экземпляр для этого (например, его вызовите EMPTY или NOT_FOUND). Преимущество состоит в том, что все вызывающие коды не должны иметь дело с нулями и не рискуют NullPointerException. При необходимости может быть логический метод, который говорит isFound() (возвращает true, кроме этого экземпляра). И коды, которые действительно должны отличаться от того, что ценности от других по-прежнему могут, в то время как те, которые не заботятся, просто передают экземпляр вокруг без знания этого частного случая.

Ответ 9

Возможно, вы не должны использовать Enum? Если вам регулярно приходится иметь дело со значениями, которые не определены в вашем Enum, возможно, вы должны использовать что-то вроде HashMap < String, Fruit > Затем вы можете использовать containsKey(), чтобы узнать, существует ли какой-либо конкретный ключ.

Ответ 10

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

Пример:

public enum Fruit {
   public Fruit getValueOf(String name) {
        for (Fruit fruit : Fruit.values()) {
           if (fruit.fruitname.equals(name))
               return fruit;
           }
        }
        return UNKNOWN;
   }
   ...
}

Ответ 11

Вы также можете сделать так: Все перечисления в одном классе, например:

public class EnumProto {

    public static Class<?>[] l;

    public static enum Severity {
        UNKNOWN_SEVERITY
    }

    public static  enum UserType {
        UNKNOWN_USER_TYPE,
        INTERNAL_EMPLOYEE ,
        EXTERNAL_PARTY
    }

    public static enum Channel {
        UNKNOWN_CHANNEL,
        CALL,
        EMAIL,
        WEBFORM,
        FAX
    }

//You can add more enum classes
}

В другом родовом классе вы можете получить что-то вроде этого:

public class Mapper {
    /**
     * This method returns all names of an enum
     * @param e
     * @return
     */
    public static String[] getEnumNames(Class<? extends Enum<?>> e) {
        return Arrays.stream(e.getEnumConstants()).map(Enum::name).toArray(String[]::new);
    }

    /**
     * This method returns all the enum classes from a class
     * @return
     */
    public static Class<?>[] getENumClasses(){
        Class<?>[] x = EnumProto.class.getClasses();
        return x;
    }

    /**
     *This utility performs following:
     *- will get all enum classes from EnumProto
     *- will get all names against all classes
     *- checks against all names of enum class and returns true if name matches else returns false
     * @param enumClass
     * @param value
     * @return
     */
    public static Boolean enumValidator(String enumClass, String value) {
        Boolean bool=false;
        EnumProto.l = getENumClasses();
        for (Class x : EnumProto.l) {
            if (x.getSimpleName().equals(enumClass)) {
                try {
                    String enumNames[] = getEnumNames(x);
                    if ( ArrayUtils.contains( enumNames, value ) ) {
                        bool=true;
                        return bool;
                    }
                } catch (ClassCastException e) {
                }
            }
        }
        return bool;
    }

    /**
     * Driver method for testing purpose
     * @param args
     */
    public static void main(String args[]){
        System.out.println(enumValidator(EnumProto.Channel.class.getSimpleName(),"CALL"));
    }
}

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

Ответ 12

В Oracle JDK (пробовал с JDK 10.0.1) класс Class имеет поле enumConstantDirectory. Это поле имеет тип Map<String, T> для Class<T>. Он хранит константы перечисления T по именам. После инициализации класса enum enumConstantDirectory все еще пуст. При первом вызове Enum.valueOf(Class<T> enumType, String name) все константы данного перечисления T сохраняются в enumConstantDirectory.

Поскольку у каждого класса enum уже есть свое собственное отображение, мы можем попытаться использовать его вместо создания дополнительного локального отображения для /some/every enum/s.

Сначала я реализовал служебный класс:

  public class Enums {

    private static final Field DIRECTORY_FIELD;

    static {
      try {
        DIRECTORY_FIELD = Class.class.getDeclaredField("enumConstantDirectory");
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    public static <T extends Enum<T>> T valueOfOrDefault(Class<T> enumType, String name, T defaultValue) throws Exception {
      return getEnumConstantDirectory(enumType).getOrDefault(name, defaultValue);
    }

    public static <T extends Enum<T>> boolean hasValueFor(Class<T> enumType, String name) throws Exception {
      Map<String, T> enumConstantDirectory = getEnumConstantDirectory(enumType);
      return enumConstantDirectory.containsKey(name);
    }

    private static <T extends Enum<T>> Map<String, T> getEnumConstantDirectory(Class<T> enumType) throws Exception {
      try {
        DIRECTORY_FIELD.setAccessible(true);
        Map<String, T> enumConstantDirectory = (Map<String, T>) DIRECTORY_FIELD.get(enumType);
        return enumConstantDirectory;
      }
      finally {
        DIRECTORY_FIELD.setAccessible(false);
      }
    }

  }

Его можно использовать так:

  public enum Note {

    DO, RE, MI, FA, SOL, LA, SI;

    static {
      Enum.valueOf(Note.class, Note.DO.name());
    }

    public static Note valueOfOrDefault(String name, Note defaultValue) throws Exception {
      return Enums.valueOfOrDefault(Note.class, name, defaultValue);
    }

    public static <T extends Enum<T>> boolean hasValueFor(String name) throws Exception {
      return Enums.hasValueFor(Note.class, name);
    }

  }

Подводя итог:
Как правило, можно проверить, представляет ли имя константу перечисления без дополнительных отображений или итерации по константам перечисления. Но, как всегда, с отражениями есть известные недостатки. Кроме того, необходимо убедиться, что константы перечисления хранятся в этом классе.