web-dev-qa-db-fra.com

Vérifier si enum existe en Java

Est-il possible de vérifier si une énumération existe en la comparant à une chaîne donnée? Je n'arrive pas à trouver une telle fonction. Je pourrais simplement essayer d'utiliser la méthode valueOf et attraper une exception, mais on m'a appris que l'interception des exceptions d'exécution n'est pas une bonne pratique. Quelqu'un a des idées?

50
Danny

Je ne pense pas qu'il existe un moyen intégré de le faire sans attraper des exceptions. Vous pourriez plutôt utiliser quelque chose comme ceci:

public static MyEnum asMyEnum(String str) {
    for (MyEnum me : MyEnum.values()) {
        if (me.name().equalsIgnoreCase(str))
            return me;
    }
    return null;
}

Edit: Comme le note Jon Skeet, values() fonctionne en clonant un tableau de sauvegarde privé à chaque appel. Si les performances sont critiques, vous voudrez peut-être appeler values() une seule fois, mettre en cache la matrice et effectuer une itération.

De plus, si votre enum contient un grand nombre de valeurs, l'alternative cartographique de Jon Skeet sera probablement plus performante que n'importe quelle itération de tableau.

45
Michael Myers

Si j'ai besoin de faire cela, je construis parfois un Set<String> des noms, ou même mon propre Map<String,MyEnum> - alors vous pouvez simplement vérifier cela.

Quelques points à noter:

  • Remplissez une telle collection statique dans un initialiseur statique. Ne pas utiliser un initialiseur de variable et s’appuyer ensuite sur son exécution lors de l’exécution du constructeur d’énum - ça ne l’aura pas été! (Les constructeurs enum sont les premières choses à exécuter, avant l'initialiseur statique.)
  • Essayez d'éviter d'utiliser values() fréquemment - il doit créer et remplir un nouveau tableau à chaque fois. Pour parcourir tous les éléments, utilisez EnumSet.allOf qui est beaucoup plus efficace pour les énumérations sans grand nombre d’éléments.

Exemple de code:

import Java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    private static final Map<String, SampleEnum> nameToValueMap =
        new HashMap<String, SampleEnum>();

    static {
        for (SampleEnum value : EnumSet.allOf(SampleEnum.class)) {
            nameToValueMap.put(value.name(), value);
        }
    }

    public static SampleEnum forName(String name) {
        return nameToValueMap.get(name);
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}

Bien sûr, si vous n’avez que quelques noms, c’est probablement excessif - une solution O(n) l'emporte souvent sur une solution O(1) lorsque n est suffisamment petit. Voici une autre approche:

import Java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    // We know we'll never mutate this, so we can keep
    // a local copy.
    private static final SampleEnum[] copyOfValues = values();

    public static SampleEnum forName(String name) {
        for (SampleEnum value : copyOfValues) {
            if (value.name().equals(name)) {
                return value;
            }
        }
        return null;
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}
58
Jon Skeet

Un de mes bibliothèques préférées: Apache Commons.

Le EnumUtils peut le faire facilement. 

En suivant un exemple pour valider un Enum avec cette bibliothèque:

MyEnum strTypeEnum = null;

// test if String str is compatible with the enum 
if( EnumUtils.isValidEnum(MyEnum.class, str) ){
    strTypeEnum = MyEnum.valueOf(str);
}
31
рüффп

Je ne sais pas pourquoi quelqu'un vous a dit qu'attraper les exceptions à l'exécution était mauvais.

Utilisez valueOf et capturer IllegalArgumentException convient parfaitement pour convertir/vérifier une chaîne en une énumération.

5
nos

Basé sur la réponse de Jon Skeet, j'ai créé un cours qui permet de le faire facilement au travail:

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import Java.util.EnumSet;
import Java.util.HashSet;
import Java.util.Map;
import Java.util.Set;

/**
 * <p>
 * This permits to easily implement a failsafe implementation of the enums's valueOf
 * Better use it inside the enum so that only one of this object instance exist for each enum...
 * (a cache could solve this if needed)
 * </p>
 *
 * <p>
 * Basic usage exemple on an enum class called MyEnum:
 *
 *   private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
 *   public static MyEnum failSafeValueOf(String enumName) {
 *       return FAIL_SAFE.valueOf(enumName);
 *   }
 *
 * </p>
 *
 * <p>
 * You can also use it outside of the enum this way:
 *   FailSafeValueOf.create(MyEnum.class).valueOf("EnumName");
 * </p>
 *
 * @author Sebastien Lorber <i>([email protected])</i>
 */
public class FailSafeValueOf<T extends Enum<T>> {

    private final Map<String,T> nameToEnumMap;

    private FailSafeValueOf(Class<T> enumClass) {
        Map<String,T> map = Maps.newHashMap();
        for ( T value : EnumSet.allOf(enumClass)) {
            map.put( value.name() , value);
        }
        nameToEnumMap = ImmutableMap.copyOf(map);
    }

    /**
     * Returns the value of the given enum element
     * If the 
     * @param enumName
     * @return
     */
    public T valueOf(String enumName) {
        return nameToEnumMap.get(enumName);
    }

    public static <U extends Enum<U>> FailSafeValueOf<U> create(Class<U> enumClass) {
        return new FailSafeValueOf<U>(enumClass);
    }

}

Et le test unitaire:

import org.testng.annotations.Test;

import static org.testng.Assert.*;


/**
 * @author Sebastien Lorber <i>([email protected])</i>
 */
public class FailSafeValueOfTest {

    private enum MyEnum {
        TOTO,
        TATA,
        ;

        private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
        public static MyEnum failSafeValueOf(String enumName) {
            return FAIL_SAFE.valueOf(enumName);
        }
    }

    @Test
    public void testInEnum() {
        assertNotNull( MyEnum.failSafeValueOf("TOTO") );
        assertNotNull( MyEnum.failSafeValueOf("TATA") );
        assertNull( MyEnum.failSafeValueOf("TITI") );
    }

    @Test
    public void testInApp() {
        assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TOTO") );
        assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TATA") );
        assertNull( FailSafeValueOf.create(MyEnum.class).valueOf("TITI") );
    }

}

Notez que j’ai utilisé Guava pour créer une carte ImmutableMap mais qu’il est possible d’utiliser une carte normale, car cette carte n’est jamais renvoyée ...

5
Sebastien Lorber

La plupart des réponses suggèrent d'utiliser une boucle avec equals pour vérifier si l'enum existe ou d'utiliser try/catch avec enum.valueOf (). Je voulais savoir quelle méthode est plus rapide et l'ai essayée. Je ne suis pas très bon en analyse comparative, alors corrigez-moi si je fais des erreurs.

Voici le code de ma classe principale:

    package enumtest;

public class TestMain {

    static long timeCatch, timeIterate;
    static String checkFor;
    static int corrects;

    public static void main(String[] args) {
        timeCatch = 0;
        timeIterate = 0;
        TestingEnum[] enumVals = TestingEnum.values();
        String[] testingStrings = new String[enumVals.length * 5];
        for (int j = 0; j < 10000; j++) {
            for (int i = 0; i < testingStrings.length; i++) {
                if (i % 5 == 0) {
                    testingStrings[i] = enumVals[i / 5].toString();
                } else {
                    testingStrings[i] = "DOES_NOT_EXIST" + i;
                }
            }

            for (String s : testingStrings) {
                checkFor = s;
                if (tryCatch()) {
                    ++corrects;
                }
                if (iterate()) {
                    ++corrects;
                }
            }
        }

        System.out.println(timeCatch / 1000 + "us for try catch");
        System.out.println(timeIterate / 1000 + "us for iterate");
        System.out.println(corrects);
    }

    static boolean tryCatch() {
        long timeStart, timeEnd;
        timeStart = System.nanoTime();
        try {
            TestingEnum.valueOf(checkFor);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        } finally {
            timeEnd = System.nanoTime();
            timeCatch += timeEnd - timeStart;
        }

    }

    static boolean iterate() {
        long timeStart, timeEnd;
        timeStart = System.nanoTime();
        TestingEnum[] values = TestingEnum.values();
        for (TestingEnum v : values) {
            if (v.toString().equals(checkFor)) {
                timeEnd = System.nanoTime();
                timeIterate += timeEnd - timeStart;
                return true;
            }
        }
        timeEnd = System.nanoTime();
        timeIterate += timeEnd - timeStart;
        return false;
    }
}

Cela signifie que chaque méthode exécute 50000 fois la longueur de l'énum J'ai exécuté ce test plusieurs fois, avec 10, 20, 50 et 100 constantes d'énum . Voici les résultats:

  • 10: try/catch: 760ms | itération: 62ms
  • 20: try/catch: 1671ms | itération: 177ms
  • 50: try/catch: 3113ms | itération: 488ms
  • 100: try/catch: 6834ms | itération: 1760ms

Ces résultats n'étaient pas exacts. Lorsque vous l'exécutez à nouveau, il y a une différence allant jusqu'à 10% dans les résultats, mais ils suffisent à montrer que la méthode try/catch est beaucoup moins efficace, en particulier pour les petits enums. 

3
Alexander Daum

Vous pouvez également utiliser Goyave et faire quelque chose comme ceci:

// This method returns enum for a given string if it exists, otherwise it returns default enum.
private MyEnum getMyEnum(String enumName) {
  // It is better to return default instance of enum instead of null
  return hasMyEnum(enumName) ? MyEnum.valueOf(enumName) : MyEnum.DEFAULT;
}

// This method checks that enum for a given string exists.
private boolean hasMyEnum(String enumName) {
  return Iterables.any(Arrays.asList(MyEnum.values()), new Predicate<MyEnum>() {
    public boolean apply(MyEnum myEnum) {
      return myEnum.name().equals(enumName);
    }
  }); 
}

Dans la deuxième méthode, j'utilise la bibliothèque guava ( Google Guava ) qui fournit une classe très utile Iterables. En utilisant la méthode Iterables.any (), nous pouvons vérifier si une valeur donnée existe dans un objet liste. Cette méthode nécessite deux paramètres: une liste et un objet Predicate. J'ai d'abord utilisé la méthode Arrays.asList () pour créer une liste avec tous les enums. Après cela, j'ai créé un nouvel objet Predicate qui permet de vérifier si un élément donné (enum dans notre cas) satisfait à la condition de la méthode apply. Si cela se produit, la méthode Iterables.any () renvoie la valeur vraie.

1
Dawid Stępień

Depuis Java 8, nous pourrions utiliser streams au lieu de boucles for. De plus, il peut être approprié de renvoyer un Facultatif si l'énumération n'a pas d'instance portant un tel nom.

J'ai mis au point les trois alternatives suivantes sur la façon de rechercher une énumération:

private enum Test {
    TEST1, TEST2;

    public Test fromNameOrThrowException(String name) {
        return Arrays.stream(values())
                .filter(e -> e.name().equals(name))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("No enum with name " + name));
    }

    public Test fromNameOrNull(String name) {
        return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst().orElse(null);
    }

    public Optional<Test> fromName(String name) {
        return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst();
    }
}
0
Magnilex