Remplacer valueof () et toString () dans l'énumération Java

122

Les valeurs dans my enumsont des mots qui doivent contenir des espaces, mais les énumérations ne peuvent pas avoir d'espaces dans leurs valeurs, donc tout est groupé. Je veux remplacer toString()pour ajouter ces espaces là où je le dis.

Je veux également que l'énumération fournisse l'énumération correcte lorsque j'utilise valueOf()sur la même chaîne à laquelle j'ai ajouté les espaces.

Par exemple:

public enum RandomEnum
{
     StartHere,
     StopHere
}

Appel toString()sur RandomEnumdont la valeur est StartHererenvoie la chaîne "Start Here". L'appel valueof()sur cette même chaîne ( "Start Here") renvoie la valeur enum StartHere.

Comment puis-je faire ceci?

WildBamaBoy
la source
2
Ajoutez la balise "Java" pour obtenir plus de commentaires / réponses.
Java42
duplication possible de l' implémentation de toString sur les énumérations Java
Steve Chambers

Réponses:

197

Vous pouvez essayer ce code. Étant donné que vous ne pouvez pas remplacer la valueOfméthode, vous devez définir une méthode personnalisée ( getEnumdans l'exemple de code ci-dessous) qui renvoie la valeur dont vous avez besoin et modifiez votre client pour utiliser cette méthode à la place.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private String value;

    RandomEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

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

    public static RandomEnum getEnum(String value) {
        for(RandomEnum v : values())
            if(v.getValue().equalsIgnoreCase(value)) return v;
        throw new IllegalArgumentException();
    }
}
Jugal Shah
la source
4
getEnum peut être raccourci si vous effectuez les opérations suivantes: v.getValue (). equals (value); le contrôle nul peut être omis.
rtcarlson
Vous pouvez également créer paresseusement une carte et la réutiliser, mais méfiez-vous des problèmes de thread lors de sa création.
Maarten Bodewes
11
ne fonctionne pas dans un scénario où vous ne pouvez pas changer l'appel de la méthode valueOf () en getValue (), par exemple lorsque vous définissez certains champs via les annotations Hibernate à mapper aux valeurs d'énumération
mmierins
Jusqu'à ce que les Enums soient corrigés en Java, @Bat a une solution plus appropriée et plus conviviale pour OO. name () ne doit pas être définitif.
Andrew T Finnell
au lieu de for, vous pouvez utiliser valueOf (RandomEnum.class, value);
Wojtek
22

Essayez ceci, mais je ne suis pas sûr que cela fonctionnera partout :)

public enum MyEnum {
    A("Start There"),
    B("Start Here");

    MyEnum(String name) {
        try {
            Field fieldName = getClass().getSuperclass().getDeclaredField("name");
            fieldName.setAccessible(true);
            fieldName.set(this, name);
            fieldName.setAccessible(false);
        } catch (Exception e) {}
    }
}
Chauve souris
la source
18
Pourquoi suis-je le seul à qui ce genre de trucs a l'air mauvais? Je veux dire que ça a l'air vraiment cool, mais quelqu'un sans contexte qui lit ce code ressemblerait probablement à "WTF?".
Nicolas Guillaume
11
@NicolasGuillaume C'est le genre de code qui, s'il était implémenté, aurait désespérément besoin d'un commentaire expliquant pourquoi il est là et quel problème le programmeur essayait de résoudre. :-)
Ti Strga
9
Ne pas attraper une exception générique, car cela pourrait être OutOfMemory ou quoi
crusy
2
C'est une idée terrible. La possibilité d'une erreur silencieuse sans indication ni récupération n'est pas la moindre. Aussi maintenant la valeur "nom" et la valeur symbolique dans le code (par exemple A, B) sont différentes. +2 pour être intelligent. -200 pour être terriblement intelligent. Il n'y a aucun moyen que j'approuverais jamais cela une révision de code.
pedorro
1
@pedorro Cela ne semble pas être aussi terrible que vous le prétendez. Quiconque comprend que tout ce que fait le comité Java ne doit pas être considéré comme un évangile le ferait passer dans une revue de code. Il est infiniment pire d'avoir à réécrire puis à maintenir toutes les méthodes de l'utilitaire Enum, car name () ne peut pas être remplacé. Au lieu d'attraper l'exception et de l'avaler, une exception RuntimeException doit être lancée et c'est bien.
Andrew T Finnell
14

Vous pouvez utiliser une carte statique dans votre énumération qui mappe des chaînes à des constantes d'énumération. Utilisez-le dans une méthode statique 'getEnum'. Cela évite d'avoir à parcourir les énumérations chaque fois que vous souhaitez en obtenir une à partir de sa valeur String.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private final String strVal;
    private RandomEnum(String strVal) {
        this.strVal = strVal;
    }

    public static RandomEnum getEnum(String strVal) {
        if(!strValMap.containsKey(strVal)) {
            throw new IllegalArgumentException("Unknown String Value: " + strVal);
        }
        return strValMap.get(strVal);
    }

    private static final Map<String, RandomEnum> strValMap;
    static {
        final Map<String, RandomEnum> tmpMap = Maps.newHashMap();
        for(final RandomEnum en : RandomEnum.values()) {
            tmpMap.put(en.strVal, en);
        }
        strValMap = ImmutableMap.copyOf(tmpMap);
    }

    @Override
    public String toString() {
        return strVal;
    }
}

Assurez-vous simplement que l'initialisation statique de la carte se produit sous la déclaration des constantes enum.

BTW - ce type 'ImmutableMap' provient de l'API Google guava, et je le recommande vivement dans des cas comme celui-ci.


EDIT - Par les commentaires:

  1. Cette solution suppose que chaque valeur de chaîne attribuée est unique et non nulle. Étant donné que le créateur de l'énumération peut contrôler cela et que la chaîne correspond à la valeur d'énumération unique et non nulle, cela semble être une restriction sûre.
  2. J'ai ajouté la méthode 'toSTring ()' comme demandé dans la question
pedorro
la source
1
J'ai reçu quelques votes négatifs sur cette réponse - n'importe qui voudrait expliquer pourquoi cette réponse est mauvaise? Je suis juste curieux de savoir ce que les gens pensent.
pedorro
Cela échouera pour un scénario ayant plusieurs constantes d'énumération avec la même 'valeur' ​​comme RestartHere("replay"), ReplayHere("replay")ou aucune 'valeur' ​​du tout comme PauseHere, WaitHere(c'est-à-dire que l'énumération a un constructeur par défaut quelque chose commeprivate RandomEnum() { this.strVal = null; }
sactiw
1
Vous devez utiliser ImmutableMap.Builder au lieu de créer un MutableMap pour build + ImmutableMap :: copyOf.
Bryan Correll
Cela semble intéressant, mais si l'énumération n'a que quelques valeurs différentes, il ne faudra pas longtemps pour les parcourir. Cela ne vaut probablement la peine que si l'énumération a beaucoup de valeurs.
Ben Thurley
13

Et une implémentation Java 8? (null peut être remplacé par votre Enum par défaut)

public static RandomEnum getEnum(String value) {
    List<RandomEnum> list = Arrays.asList(RandomEnum.values());
    return list.stream().filter(m -> m.value.equals(value)).findAny().orElse(null);
}

Ou vous pouvez utiliser:

...findAny().orElseThrow(NotFoundException::new);
Corlaez
la source
2

Je ne pense pas que vous allez faire fonctionner valueOf ("Start Here"). Mais en ce qui concerne les espaces ... essayez ce qui suit ...

static private enum RandomEnum {
    R("Start There"), 
    G("Start Here"); 
    String value;
    RandomEnum(String s) {
        value = s;
    }
}

System.out.println(RandomEnum.G.value);
System.out.println(RandomEnum.valueOf("G").value);

Start Here
Start Here
Java42
la source
2

Ce qui suit est une belle alternative générique à valueOf ()

public static RandomEnum getEnum(String value) {
  for (RandomEnum re : RandomEnum.values()) {
    if (re.description.compareTo(value) == 0) {
      return re;
    }
  }
  throw new IllegalArgumentException("Invalid RandomEnum value: " + value);
}
exception
la source
est-ce que l'utilisation est compareTo()plus élégante que equals()pour les chaînes?
Betlista
L'élégance n'est pas un problème - je suis d'accord que .equals()cela fonctionnerait bien. Pourrait utiliser ==si vous le souhaitez. (Bien qu'une bonne raison de ne pas le faire, c'est de remplacer l'énumération par une classe.)
exception
3
@exception N'UTILISEZ JAMAIS == pour les comparaisons de chaînes car il effectuera une vérification d'égalité d'objet alors que vous avez besoin d'une vérification d'égalité de contenu et pour cela, utilisez la méthode equals () de la classe String.
sactiw
2

Vous avez toujours une option pour implémenter dans votre énumération ceci:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Andrey Lebedenko
la source