web-dev-qa-db-fra.com

Méthode recommandée pour rechercher Java Enum

Nous avons une API REST dans laquelle les clients peuvent fournir des paramètres représentant les valeurs définies sur le serveur dans Java Enums.

Pour que nous puissions fournir une erreur descriptive, nous ajoutons cette méthode lookup à chaque Enum. On dirait que nous ne faisons que copier du code (mauvais). Y a-t-il une meilleure pratique? 

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

Update : Le message d'erreur par défaut fourni par valueOf(..) serait No enum const class a.b.c.MyEnum.BadValue. Je voudrais fournir une erreur plus descriptive de l'API. 

38
Marcus Leon

Vous pouvez probablement implémenter la méthode statique générique lookup.

Ainsi

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

Ensuite vous pouvez

public enum MyEnum {
   static public MyEnum lookup(String id) {
       return LookupUtil.lookup(MyEnum.class, id);
   }
}

ou appelez explicitement la méthode de recherche de classe d'utilitaire.

33
Mykola Golubyev

On dirait que vous avez une mauvaise pratique ici mais pas où vous pensez.

Saisir une IllegalArgumentException pour en redemander une autre RuntimeException avec un message plus clair peut sembler une bonne idée mais ce n’est pas le cas. Parce que cela signifie que vous vous souciez des messages dans vos exceptions.

Si vous vous souciez des messages dans vos exceptions, cela signifie alors que votre utilisateur voit vos exceptions. C'est mauvais.

Si vous souhaitez fournir un message d'erreur explicite à votre utilisateur, vous devez vérifier la validité de la valeur enum lors de l'analyse de l'entrée utilisateur et envoyer le message d'erreur approprié dans la réponse si l'entrée est incorrecte.

Quelque chose comme:

// 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;
    }
}
12
Vincent Robert

Pourquoi devons-nous écrire ce code à 5 lignes?

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"); 
}
}
3
Kannan Ekanath

Si vous souhaitez que la recherche soit insensible à la casse, vous pouvez parcourir les valeurs pour la rendre un peu plus conviviale:

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

update: Comme GreenTurtle l'a bien remarqué, les erreurs suivantes sont incorrectes


Je voudrais juste écrire

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

C'est peut-être moins performant que de capturer une exception d'exécution, mais cela rend le code beaucoup plus propre. Catching de telles exceptions est toujours une mauvaise idée, car elle est sujette à des diagnostics erronés ..__ Que se passe-t-il lorsque la récupération de la valeur comparée elle-même provoque une exception IllegalArgumentException? Ceci serait alors traité comme une valeur non correspondante pour l'énumérateur.

2
makrom

Nous faisons tous nos énumérations comme celle-ci quand il s’agit de Rest/Json, etc. ..__ L’avantage est que l’erreur est lisible par l’homme et vous donne également la liste des valeurs acceptées. Nous utilisons une méthode personnalisée MyEnum.fromString au lieu de MyEnum.valueOf, espérons que cela vous aidera.

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

donc par exemple si vous appelez

MyEnum value = MyEnum.fromString("X");

vous obtiendrez une exception IllegalArgumentException avec le message suivant:

'X' n'a pas de valeur correspondante. Valeurs acceptées: [A, B, C, D]

vous pouvez remplacer l'exception IllegalArgumentException par une exception personnalisée.

2
Radu Sebastian LAZIN

Le message d'erreur dans IllegalArgumentException est déjà suffisamment descriptif. 

Votre méthode crée une exception générique à partir d'une exception spécifique avec le même message simplement reformulé. Un développeur préfère le type d'exception spécifique et peut gérer le cas de manière appropriée au lieu d'essayer de gérer l'exception RuntimeException.

Si l'intention est de rendre le message plus convivial, les références aux valeurs d'énums ne leur sont de toute façon pas pertinentes. Laissez le code de l'interface utilisateur déterminer ce qui doit être affiché à l'utilisateur, et le développeur de l'interface utilisateur serait mieux avec l'exception IllegalArgumentException.

2
Robin

Guava fournit également une telle fonction qui retournera une Optional si une énumération est introuvable 

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