Comment rechercher une énumération Java à partir de sa valeur String?

164

Je voudrais rechercher une énumération à partir de sa valeur de chaîne (ou éventuellement de toute autre valeur). J'ai essayé le code suivant mais il n'autorise pas la statique dans les initialiseurs. Y a-t-il un moyen simple?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};
peter.murray.rust
la source
IIRC, qui donne un NPE car l'initialisation statique se fait de haut en bas (c'est-à-dire que les constantes d'énumération en haut sont construites avant de passer à l' stringMapinitialisation). La solution habituelle consiste à utiliser une classe imbriquée.
Tom Hawtin - tackline
Merci à tous pour cette réponse rapide. (FWIW je n'ai pas trouvé les Sun Javadocs très utiles pour ce problème).
peter.murray.rust
C'est vraiment un problème de langue qu'un problème de bibliothèque. Cependant, je pense que les documents API sont plus lus que le JLS (bien que peut-être pas par les concepteurs de langage), donc des choses comme celles-ci devraient probablement avoir plus d'importance dans les documents java.lang.
Tom Hawtin - tackline

Réponses:

241

Utilisez la valueOfméthode qui est automatiquement créée pour chaque Enum.

Verbosity.valueOf("BRIEF") == Verbosity.BRIEF

Pour les valeurs arbitraires, commencez par:

public static Verbosity findByAbbr(String abbr){
    for(Verbosity v : values()){
        if( v.abbr().equals(abbr)){
            return v;
        }
    }
    return null;
}

Ne passez à l'implémentation de Map que si votre profileur vous le demande.

Je sais que cela itère sur toutes les valeurs, mais avec seulement 3 valeurs d'énumération, cela ne vaut guère aucun autre effort, en fait à moins que vous n'ayez beaucoup de valeurs, je ne me soucierais pas d'une carte, ce sera assez rapide.

Gareth Davis
la source
merci - et je peux utiliser la conversion de cas si je sais que les valeurs sont toujours distinctes
peter.murray.rust
Vous pouvez accéder directement au membre de classe 'abbr' donc au lieu de "v.abbr ()" vous pouvez utiliser "v.abbr.equals ...".
Amio.io
7
De plus, pour les valeurs arbitraires (le deuxième cas), envisagez de lancer un IllegalArgumentExceptionau lieu de renvoyer null ( c'est- à- dire lorsqu'aucune correspondance n'est trouvée) - c'est ainsi que le JDK le Enum.valueOf(..)fait, de toute façon.
Priidu Neemre
135

Tu es proche. Pour les valeurs arbitraires, essayez quelque chose comme ce qui suit:

public enum Day { 

    MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
    THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

    private final String abbreviation;

    // Reverse-lookup map for getting a day from an abbreviation
    private static final Map<String, Day> lookup = new HashMap<String, Day>();

    static {
        for (Day d : Day.values()) {
            lookup.put(d.getAbbreviation(), d);
        }
    }

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day get(String abbreviation) {
        return lookup.get(abbreviation);
    }
}
Lyle
la source
4
En raison de problèmes de chargeur de classe, cette méthode ne fonctionnera pas de manière fiable. Je ne le recommande pas et je l'ai vu échouer régulièrement car la carte de recherche n'est pas prête avant d'être consultée. Vous devez mettre la "recherche" en dehors de l'énumération ou dans une autre classe pour qu'elle se charge en premier.
Adam Gent
7
@Adam Gent: Il y a un lien ci-dessous vers le JLS où cette construction exacte est posée comme exemple. Il dit que l'initialisation statique se produit de haut en bas: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena
3
Oui, java moderne comme java 8 a corrigé le modèle de mémoire. Cela étant dit, les agents d'échange à chaud comme jrebel sont toujours en panne si vous intégrez l'initialisation à cause de références circulaires.
Adam Gent
@Adam Gent: Hmmm. J'apprends quelque chose de nouveau tous les jours. [S'effacer tranquillement pour refactoriser mes énumérations avec des singletons bootstrap ...] Désolé si c'est une question stupide, mais est-ce principalement un problème d'initialisation statique?
Selena
Je n'ai jamais vu de problèmes avec le bloc de code statique non initialisé, mais je n'ai utilisé que Java 8 depuis que j'ai commencé à Java, en 2015. Je viens de faire un petit test en utilisant JMH 1.22 et en utilisant a HashMap<>pour stocker les énumérations s'est avéré être plus de 40% plus rapide qu'une boucle de plancher.
cbaldan
21

avec Java 8, vous pouvez réaliser de cette manière:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
Lam Le
la source
Je lancerais une exception plutôt que de revenir null. Mais cela dépend aussi du cas d'utilisation
alexander
12

La réponse de @ Lyle est plutôt dangereuse et je l'ai vu ne pas fonctionner en particulier si vous faites de l'énumération une classe interne statique. Au lieu de cela, j'ai utilisé quelque chose comme celui-ci qui chargera les cartes BootstrapSingleton avant les énumérations.

Modifier cela ne devrait plus être un problème avec les JVM modernes (JVM 1.6 ou supérieur) mais je pense qu'il y a encore des problèmes avec JRebel mais je n'ai pas eu l'occasion de le tester à nouveau .

Chargez-moi d'abord:

   public final class BootstrapSingleton {

        // Reverse-lookup map for getting a day from an abbreviation
        public static final Map<String, Day> lookup = new HashMap<String, Day>();
   }

Maintenant, chargez-le dans le constructeur enum:

   public enum Day { 
        MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
        THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

        private final String abbreviation;

        private Day(String abbreviation) {
            this.abbreviation = abbreviation;
            BootstrapSingleton.lookup.put(abbreviation, this);
        }

        public String getAbbreviation() {
            return abbreviation;
        }

        public static Day get(String abbreviation) {
            return lookup.get(abbreviation);
        }
    }

Si vous avez une énumération interne, vous pouvez simplement définir la carte au-dessus de la définition d'énumération et cela (en théorie) devrait être chargé avant.

Adam Gent
la source
3
Il faut définir «dangereux» et pourquoi une implémentation de classe interne est importante. Il s'agit davantage d'un problème de définition statique de haut en bas, mais il y a du code de travail dans une autre réponse associée avec un lien vers cette question qui utilise une carte statique sur l'instance Enum elle-même avec une méthode "get" de la valeur définie par l'utilisateur à Type d'énumération. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague
2
@DarrellTeague, le problème d'origine était dû à d'anciennes JVM (antérieures à 1.6) ou à d'autres JVM (IBM) ou à des échangeurs de codes à chaud (JRebel). Le problème ne devrait pas se produire dans les JVM modernes, je peux donc simplement supprimer ma réponse.
Adam Gent
Il est bon de noter qu'en effet, c'est possible grâce aux implémentations personnalisées du compilateur et aux optimisations d'exécution ... l'ordre pourrait être reséquencé, entraînant une erreur. Cependant, cela semble enfreindre les spécifications.
Darrell Teague
7

Et vous ne pouvez pas utiliser valueOf () ?

Edit: Btw, rien ne vous empêche d'utiliser static {} dans une énumération.

Fredrik
la source
Ou le synthétique valueOfsur la classe enum, vous n'avez donc pas besoin de spécifier la classe enum Class.
Tom Hawtin - tackline
Bien sûr, il n'avait tout simplement pas d'entrée javadoc, il était donc difficile de le lier.
Fredrik
1
Vous pouvez utiliser valueOf () mais le nom doit correspondre de manière identique à celui défini dans la déclaration d'énumération. L'une ou l'autre des deux méthodes de recherche ci-dessus peut être modifiée pour utiliser .equalsIgnoringCase () et avoir un peu plus de robustesse à l'erreur.
@leonardo True. Si cela ajoute de la robustesse ou simplement une tolérance aux pannes, cela est discutable. Dans la plupart des cas, je dirais que c'est ce dernier et dans ce cas, il vaut mieux le gérer ailleurs et toujours utiliser valueOf () d'Enum.
Fredrik
4

Au cas où cela aiderait les autres, l'option que je préfère, qui n'est pas répertoriée ici, utilise la fonctionnalité Cartes de Guava :

public enum Vebosity {
    BRIEF("BRIEF"),
    NORMAL("NORMAL"),
    FULL("FULL");

    private String value;
    private Verbosity(final String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    private static ImmutableMap<String, Verbosity> reverseLookup = 
            Maps.uniqueIndex(Arrays.asList(Verbosity.values()), Verbosity::getValue);

    public static Verbosity fromString(final String id) {
        return reverseLookup.getOrDefault(id, NORMAL);
    }
}

Avec la valeur par défaut, vous pouvez utiliser null, vous pouvez throw IllegalArgumentExceptionou vous fromStringpouvez retourner un Optionalcomportement, quel que soit le comportement que vous préférez.

Tchad Befus
la source
3

depuis java 8 vous pouvez initialiser la carte en une seule ligne et sans bloc statique

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
Adrian
la source
+1 Excellente utilisation des flux, et très concis sans sacrifier la lisibilité! Je n'avais pas non plus vu cette utilisation Function.identity()auparavant, je la trouve beaucoup plus attrayante textuellement que e -> e.
Matsu Q.
0

Peut-être, jetez un œil à ceci. Cela fonctionne pour moi. Le but de ceci est de rechercher 'RED' avec '/ red_color'. Déclarer a static mapet ne charger le enums qu'une seule fois apporterait des avantages en termes de performances si les enums sont nombreux.

public class Mapper {

public enum Maps {

    COLOR_RED("/red_color", "RED");

    private final String code;
    private final String description;
    private static Map<String, String> mMap;

    private Maps(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return name();
    }

    public String getDescription() {
        return description;
    }

    public String getName() {
        return name();
    }

    public static String getColorName(String uri) {
        if (mMap == null) {
            initializeMapping();
        }
        if (mMap.containsKey(uri)) {
            return mMap.get(uri);
        }
        return null;
    }

    private static void initializeMapping() {
        mMap = new HashMap<String, String>();
        for (Maps s : Maps.values()) {
            mMap.put(s.code, s.description);
        }
    }
}
}

Veuillez donner votre avis.

Midhun
la source
-1

Vous pouvez définir votre Enum comme le code suivant:

public enum Verbosity 
{
   BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
   private int value;

   public int getValue()
   {
     return this.value;
   } 

   public static final Verbosity getVerbosityByValue(int value)
   {
     for(Verbosity verbosity : Verbosity.values())
     {
        if(verbosity.getValue() == value)
            return verbosity ;
     }

     return ACTION_NOT_VALID;
   }

   @Override
   public String toString()
   {
      return ((Integer)this.getValue()).toString();
   }
};

Voir le lien suivant pour plus de précisions

Vinay Sharma
la source
-1

Si vous voulez une valeur par défaut et ne voulez pas créer de mappes de recherche, vous pouvez créer une méthode statique pour gérer cela. Cet exemple gère également les recherches où le nom attendu commencerait par un nombre.

    public static final Verbosity lookup(String name) {
        return lookup(name, null);
    }

    public static final Verbosity lookup(String name, Verbosity dflt) {
        if (StringUtils.isBlank(name)) {
            return dflt;
        }
        if (name.matches("^\\d.*")) {
            name = "_"+name;
        }
        try {
            return Verbosity.valueOf(name);
        } catch (IllegalArgumentException e) {
            return dflt;
        }
    }

Si vous en avez besoin sur une valeur secondaire, vous devez simplement créer la carte de recherche en premier, comme dans certaines des autres réponses.

ScrappyDev
la source
-1
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}

Vahap Gencdal
la source
-2

Vous pouvez utiliser la Enum::valueOf()fonction comme suggéré par Gareth Davis & Brad Mace ci-dessus, mais assurez-vous de gérer le IllegalArgumentExceptionqui serait lancé si la chaîne utilisée n'est pas présente dans l'énumération.

Mayank Butpori
la source