web-dev-qa-db-fra.com

Java enum meilleure pratique de recherche inversée

Je l'ai vu suggéré sur un blog que ce qui suit était une façon raisonnable de faire une "recherche inversée" en utilisant la getCode(int) dans un Java énumération:

public enum Status {
    WAITING(0),
    READY(1),
    SKIPPED(-1),
    COMPLETED(5);

    private static final Map<Integer,Status> lookup 
            = new HashMap<Integer,Status>();

    static {
        for(Status s : EnumSet.allOf(Status.class))
            lookup.put(s.getCode(), s);
    }

    private int code;

    private Status(int code) {
        this.code = code;
    }

    public int getCode() { return code; }

    public static Status get(int code) { 
        return lookup.get(code); 
    }
}

Pour moi, la carte statique et l'initialiseur statique ressemblent tous les deux à une mauvaise idée, et ma première pensée serait de coder la recherche comme suit:

public enum Status {
    WAITING(0),
    READY(1),
    SKIPPED(-1),
    COMPLETED(5);

    private int code;

    private Status(int code) {
        this.code = code;
    }

    public int getCode() { return code; }

    public static Status get(int code) { 
        for(Status s : values()) {
            if(s.code == code) return s;
        }
        return null;
    }
}

Existe-t-il des problèmes évidents avec l'une ou l'autre méthode, et existe-t-il une méthode recommandée pour implémenter ce type de recherche?

70
Armand

Maps.uniqueIndex de Google Guava est assez pratique pour créer des cartes de recherche.

Mise à jour: voici un exemple utilisant Maps.uniqueIndex avec Java 8:

public enum MyEnum {
    A(0), B(1), C(2);

    private static final Map<Integer, MyEnum> LOOKUP = Maps.uniqueIndex(
                Arrays.asList(MyEnum.values()),
                MyEnum::getStatus
    );    

    private final int status;

    MyEnum(int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }

    @Nullable
    public static MyEnum fromStatus(int status) {
        return LOOKUP.get(status);
    }
}
24
user638455

Bien qu'il ait un temps système plus élevé, la carte statique est agréable car elle offre une recherche à temps constant par code. Le temps de recherche de votre implémentation augmente linéairement avec le nombre d'éléments dans l'énumération. Pour les petites énumérations, cela ne contribuera tout simplement pas de manière significative.

Un problème avec les deux implémentations (et, sans doute, avec Java en général) est qu'il y a vraiment une valeur supplémentaire cachée qu'un Status peut prendre: null. En fonction des règles de la logique métier, il peut être judicieux de renvoyer une valeur d'énumération réelle ou de lancer un Exception lorsque la recherche "échoue".

19
Matt Ball

Voici une alternative qui peut être encore un peu plus rapide:

public enum Status {
    WAITING(0),
    READY(1),
    SKIPPED(-1),
    COMPLETED(5);

    private int code;

    private Status(int code) {
        this.code = code;
    }

    public int getCode() { return code; }

    public static Status get(int code) {
        switch(code) {
            case  0: return WAITING;
            case  1: return READY;
            case -1: return SKIPPED;
            case  5: return COMPLETED;
        }
        return null;
    }
}

Bien sûr, ce n'est pas vraiment gérable si vous voulez pouvoir ajouter plus de constantes plus tard.

8
Paŭlo Ebermann

De toute évidence, la carte fournira une recherche à temps constant, contrairement à la boucle. Dans une énumération typique avec peu de valeurs, je ne vois aucun problème avec la recherche de parcours.

5
digitaljoel

Voici une alternative Java 8 alternative (avec test unitaire):

// DictionarySupport.Java :

import org.Apache.commons.collections4.Factory;
import org.Apache.commons.collections4.map.LazyMap;

import Java.util.HashMap;
import Java.util.Map;

public interface DictionarySupport<T extends Enum<T>> {

    @SuppressWarnings("unchecked")
    Map<Class<?>,  Map<String, Object>> byCodeMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new);

    @SuppressWarnings("unchecked")
    Map<Class<?>,  Map<Object, String>> byEnumMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new);


    default void init(String code) {
        byCodeMap.get(this.getClass()).put(code, this);
        byEnumMap.get(this.getClass()).put(this, code) ;
    }

    static <T extends Enum<T>> T getByCode(Class<T> clazz,  String code) {
        clazz.getEnumConstants();
        return (T) byCodeMap.get(clazz).get(code);
    }

    default <T extends Enum<T>> String getCode() {
        return byEnumMap.get(this.getClass()).get(this);
    }
}

// Dictionary 1:
public enum Dictionary1 implements DictionarySupport<Dictionary1> {

    VALUE1("code1"),
    VALUE2("code2");

    private Dictionary1(String code) {
        init(code);
    }
}

// Dictionary 2:
public enum Dictionary2 implements DictionarySupport<Dictionary2> {

    VALUE1("code1"),
    VALUE2("code2");

    private Dictionary2(String code) {
        init(code);
    }
}

// DictionarySupportTest.Java:     
import org.testng.annotations.Test;
import static org.fest.assertions.api.Assertions.assertThat;

public class DictionarySupportTest {

    @Test
    public void teetSlownikSupport() {

        assertThat(getByCode(Dictionary1.class, "code1")).isEqualTo(Dictionary1.VALUE1);
        assertThat(Dictionary1.VALUE1.getCode()).isEqualTo("code1");

        assertThat(getByCode(Dictionary1.class, "code2")).isEqualTo(Dictionary1.VALUE2);
        assertThat(Dictionary1.VALUE2.getCode()).isEqualTo("code2");


        assertThat(getByCode(Dictionary2.class, "code1")).isEqualTo(Dictionary2.VALUE1);
        assertThat(Dictionary2.VALUE1.getCode()).isEqualTo("code1");

        assertThat(getByCode(Dictionary2.class, "code2")).isEqualTo(Dictionary2.VALUE2);
        assertThat(Dictionary2.VALUE2.getCode()).isEqualTo("code2");

    }
}
3
Vitaliy Oliynyk

Les deux voies sont parfaitement valables. Et ils ont techniquement le même temps de fonctionnement Big-Oh.

Toutefois, si vous enregistrez d'abord toutes les valeurs dans une carte, vous économisez le temps nécessaire pour parcourir l'ensemble à chaque fois que vous souhaitez effectuer une recherche. Donc, je pense que la carte statique et l'initialiseur sont un peu mieux.

0
jjnguy

Toutes les autres réponses de ce fil ont utilisé une méthode statique. Je pensais que nous devrions fournir un exemple en utilisant une méthode d'instance, comme alternative:

public class Main {
    public static void main(String[] args) {
        for (Alphabet item : Alphabet.values()) {
            System.out.println("GET: " + item.getValue() );
            System.out.println("REV: " + item.reverseLookup(item.getValue()) );
        }
    }
}


enum Alphabet {
    X(24) {
        public String reverseLookup(int o) {
            return "X";
        }
    },
    Y(25) {
        public String reverseLookup(int o) {
            return "Y";
        }
    },
    Z(26) {
        public String reverseLookup(int o) {
            return "Z";
        }
    };

    abstract String reverseLookup(int o);

    private final int value;

    Alphabet(final int newValue) {
        value = newValue;
    }

    public int getValue() { return value; }
}
0
djangofan

Dans Java 8, je voudrais simplement ajouter la méthode d'usine suivante à votre énumération et ignorer la carte de recherche.

public static Optional<Status> of(int value) {
    return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst();
}
0
ce72