web-dev-qa-db-fra.com

Vérifiez les valeurs enum valides avant d'utiliser enum

J'essaie de rechercher un ensemble Enum, sachant qu'il y aura souvent une non-correspondance qui lève une exception: j'aimerais vérifier que la valeur existe avant de lancer la recherche pour éviter les exceptions. Mon enum ressemble à quelque chose comme ça:

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

et je veux vérifier si, par exemple, "banane" est l'une de mes valeurs enum avant d'essayer d'utiliser l'énum correspondant. Je pouvais parcourir les valeurs permises en comparant ma chaîne à

Fruit.values()[i].fruitname

mais j'aimerais pouvoir faire quelque chose comme (pseduo-code):

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

Est-ce possible? Devrais-je utiliser quelque chose de plus (Tableaux? Cartes?)?

EDIT: à la fin je suis allé avec la suggestion de NawaMan, mais merci à tout le monde pour toute la contribution utile.

20
davek

Je ne connais vraiment pas de solution intégrée. Vous devrez donc peut-être l'écrire vous-même en tant que méthode statique.

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;
   }
   ...
}
24
NawaMan

Il existe un langage commun Apache lang EnumUtils.isValidEnum (). Malheureusement, sous le capot, il utilise la logique try/catch et renvoie des valeurs booléennes, mais au moins votre code est propre:

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

Vous devrez utiliser la dernière bibliothèque commons-lang3 car commons-lang 2.x ne dispose pas de cette fonction. 

18
kman

Quand je fais cela, je le greffe généralement sur mon cours d’énumération.

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

Mais:

  • Pour les petits enums, il est probablement plus efficace de parcourir la liste.

Incidemment:

  • Dans cette situation, j'aurais opté pour convention et utiliser name () car il s'agit du même nom que le nom personnalisé, à l'exception du cas (facile à corriger).
  • Cette solution est plus utile lorsque ce que vous devez rechercher est complètement différent de la valeur name ().
7
Trejkaz

Voici comment procéder en utilisant EnumSet.allOf pour remplir une carte:

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
dogbane

Je serai le contrarian ici ... Je pense que votre première impulsion (lancer une exception) est la bonne chose à faire.

Si vous vérifiez dans la logique métier plutôt que dans l'interface utilisateur, il n'y aura pas de retour d'informations de ce niveau à l'utilisateur. (Si vous ne vous enregistrez pas dans l'interface utilisateur, nous avons d'autres problèmes). Par conséquent, la bonne façon de le gérer est de lancer une exception.

Bien sûr, cela ne signifie pas que vous devez avoir une bulle d’exception jusqu’au niveau de l’interface utilisateur, ce qui court-circuite le reste de votre logique. Ce que je fais habituellement met l’affectation d’énum dans son propre petit essai et traite l’exception en réaffectant ou toute autre solution élégante que vous avez imaginée.

En bref ... vous étiez sur l'argent avec votre première pensée. Allez avec ça. Il suffit de changer votre traitement des exceptions un peu différent.

5
Rap

Ceci est ma solution. J'ai créé un ensemble pour que vous n'ayez pas à spécifier de constructeur. Cela présente également l'avantage supplémentaire que la valeur recherchée doit correspondre au cas de l'énum.

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
Yeison

En Java8, vous pouvez le faire comme ça

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

Je suis d'accord avec votre désir de ne pas créer d'exception. C’est bon pour la performance (une exception vaut mille instructions, pour construire la trace de pile), et c’est logique de dire qu’il est souvent impossible de la trouver (par conséquent, ce n’est pas un exceptionnel état).


Je pense que le for loop que vous avez mentionné est correct, si vous n’avez que quelques valeurs enum. Il aura probablement la meilleure performance de tous. Mais je comprends que tu n'en veux pas.


Vous pouvez créer une carte pour trouver vos valeurs enum, ce qui éviterait l'exception et renverrait l'énum approprié en même temps.

Mise à jour: Trejkaz a déjà posté le code qui fait cela.


Notez également que parfois, au lieu de renvoyer null comme type de retour lorsqu'aucune instance ne correspond, certaines enum ont une instance dédiée pour cela (appelez-la EMPTY ou NOT_FOUND par exemple). L'avantage est que tout code d'appel n'a pas à traiter avec des valeurs NULL, et ne risque pas NullPointerException. Si nécessaire, une méthode booléenne peut indiquer isFound() (renvoie true sauf pour cette instance). Et les codes qui auraient vraiment besoin de différencier les valeurs des autres peuvent encore, tandis que ceux qui s'en moquent ne font que transmettre l'instance sans connaître ce cas particulier.

2
KLE

Juste pour mentionner une autre possibilité qui permettra à votre code d'appel de ne pas avoir à vous soucier des exceptions ou des contrôles conditionnels est de toujours renvoyer un Fruit. Si la chaîne n'est pas trouvée, retournez Fruit.UNKNOWN, par exemple.

Exemple:

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

Peut-être que vous ne devriez pas utiliser un Enum du tout? Si vous devez régulièrement traiter des valeurs qui ne sont pas définies dans votre Enum, vous devriez peut-être utiliser quelque chose comme un HashMap <String, Fruit>. Vous pouvez ensuite utiliser containsKey () pour savoir si une clé particulière existe.

2
Tom Jefferys

Dans Oracle JDK (essayé avec JDK 10.0.1), la classe Class a le champ enumConstantDirectory. Ce champ est de type Map<String, T> pour Class<T>. Il stocke les constantes d'une énumération T par leur nom. Après l'initialisation d'une classe d'énumération, enumConstantDirectory est toujours vide. Lors du premier appel de Enum.valueOf(Class<T> enumType, String name), toutes les constantes de l'énumération donnée T sont stockées dans la enumConstantDirectory.

Étant donné que chaque classe d’énumération a déjà son propre mappage, nous pourrions essayer de l’utiliser au lieu de créer un mappage local supplémentaire pour un/some/every enum/s.

J'ai d'abord implémenté une classe utilitaire:

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

  }

Il peut être utilisé comme ceci:

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

  }

Résumer:
Il est généralement possible de vérifier si un nom représente une constante enum sans mappages supplémentaires ni itération sur les constantes enum. Mais comme toujours avec les réflexions, il y a les inconvénients connus. De plus, il est nécessaire de s'assurer que les constantes d'une énumération sont stockées dans sa classe.

0
LuCio