Meilleure pratique de recherche inversée Java

Je l’ai vu suggéré sur un blog que ce qui suit était une manière raisonnable de faire une “recherche inverse” en utilisant le getCode(int) dans un enum Java:

 public enum Status { WAITING(0), READY(1), SKIPPED(-1), COMPLETED(5); private static final Map lookup = new HashMap(); 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 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; } } 

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

Maps.uniqueIndex de Google Guava est très pratique pour créer des cartes de recherche.

Mise à jour: Voici un exemple d’utilisation de Maps.uniqueIndex avec Java 8:

 public enum MyEnum { A(0), B(1), C(2); private static final Map 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); } } 

Bien que la surcharge soit plus importante, la carte statique est intéressante car elle offre une recherche constante par code . Le temps de recherche de votre implémentation augmente linéairement avec le nombre d’éléments de l’énumération. Pour les petites énumérations, cela ne consortingbuera pas de manière significative.

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

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, cela n’est pas vraiment maintenable si vous voulez pouvoir append plus de constantes plus tard.

De toute évidence, la carte fournira une recherche en temps constant alors que la boucle ne le fera pas. Dans un enum typique avec peu de valeurs, je ne vois pas de problème avec la recherche de traversée.

Voici une alternative à Java 8 (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> { @SuppressWarnings("unchecked") Map, Map> byCodeMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new); @SuppressWarnings("unchecked") Map, Map> byEnumMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new); default void init(Ssortingng code) { byCodeMap.get(this.getClass()).put(code, this); byEnumMap.get(this.getClass()).put(this, code) ; } static > T getByCode(Class clazz, Ssortingng code) { clazz.getEnumConstants(); return (T) byCodeMap.get(clazz).get(code); } default > Ssortingng getCode() { return byEnumMap.get(this.getClass()).get(this); } } // Dictionary 1: public enum Dictionary1 implements DictionarySupport { VALUE1("code1"), VALUE2("code2"); private Dictionary1(Ssortingng code) { init(code); } } // Dictionary 2: public enum Dictionary2 implements DictionarySupport { VALUE1("code1"), VALUE2("code2"); private Dictionary2(Ssortingng 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"); } } 

Les deux manières sont parfaitement valables. Et ils ont techniquement le même temps d’exécution Big-Oh.

Toutefois, si vous sauvegardez toutes les valeurs sur une carte en premier, 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 adaptés.

En Java 8, je voudrais simplement append la méthode d’usine suivante à votre enum et ignorer la carte de recherche.

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