Проверьте допустимые значения перечисления перед использованием перечисления - PullRequest
22 голосов
/ 02 октября 2009

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

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

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

Fruit.values()[i].fruitname

но я бы хотел иметь возможность сделать что-то вроде (псевдо-код):

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

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

РЕДАКТИРОВАТЬ: в конце концов я согласился с предложением NawaMan, но спасибо всем за полезный вклад.

Ответы [ 11 ]

25 голосов
/ 02 октября 2009

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

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;
   }
   ...
}
21 голосов
/ 12 января 2012

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

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

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

8 голосов
/ 02 октября 2009

Когда я делаю это, я обычно прививаю его на свой 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 (), поскольку оно совпадает с пользовательским именем, за исключением случая (легко исправить.)
  • Это решение более полезно, когда то, что вам нужно искать, полностью отличается от значения name ().
7 голосов
/ 30 марта 2010

Вот как вы можете сделать это, используя 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);
    }
}
6 голосов
/ 23 августа 2014

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

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

}
5 голосов
/ 02 октября 2009

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

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

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

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

3 голосов
/ 28 марта 2017

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

 public static boolean isValidFruit(final String fruit) {
    return Arrays.stream(Fruit.values())
        .map(Fruit::name)
        .collect(Collectors.toSet())
        .contains(fruit);
}
2 голосов
/ 05 октября 2009

Просто чтобы упомянуть еще одну возможность, которая позволит вашему вызывающему коду не беспокоиться об исключениях или условных проверках, это всегда возвращать 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;
   }
   ...
}
2 голосов
/ 02 октября 2009

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

2 голосов
/ 02 октября 2009

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


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


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

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


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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...