2011-02-03 14 views
11

Tengo varias enumeraciones de Java que se parecen más abajo (editadas para la confidencialidad, etc.). En cada caso, tengo un método de búsqueda con el que no estoy satisfecho; en el ejemplo siguiente, es findByChannelCode.La forma correcta de buscar una enumeración por valor

public enum PresentationChannel { 
    ChannelA("A"), 
    ChannelB("B"), 
    ChannelC("C"), 
    ChannelD("D"), 
    ChannelE("E"); 

    private String channelCode; 

    PresentationChannel(String channelCode) { 
     this.channelCode = channelCode; 
    } 

    public String getChannelCode() { 
     return this.channelCode; 
    } 

    public PresentationChannel findByChannelCode(String channelCode) { 
     if (channelCode != null) { 
      for (PresentationChannel presentationChannel : PresentationChannel.values()) { 
       if (channelCode.equals(presentationChannel.getChannelCode())) { 
        return presentationChannel; 
       } 
      } 
     } 

     return null; 
    } 
} 

El problema es que me siento tonta haciendo estas búsquedas lineales cuando tan sólo pudiera estar usando un HashMap<String, PresentationChannel>. Así que pensé en la solución a continuación, pero espero que sea un poco más desordenada y, más al grano, no me importó reinventar la rueda cuando seguramente alguien más se ha encontrado con esto. Quería obtener algo de la sabia sabiduría de este grupo: ¿Cuál es la forma correcta de indexar una enumeración por valor?

Mi solución:

ImmutableMap<String, PresentationChannel> enumMap = Maps.uniqueIndex(ImmutableList.copyOf(PresentationChannel.values()), new Function<PresentationChannel, String>() { 
     public String apply(PresentationChannel input) { 
      return input.getChannelCode(); 
     }}); 

y, en la enumeración:

public static PresentationChannel findByChannelCode(String channelCode) { 
    return enumMap.get(channelCode); 
} 
+0

usted entiende que los mapas enumMap enum-> Objeto no viceversa? – bestsss

+0

¿El perfil del código muestra que la búsqueda lineal es inadecuada? – trashgod

+0

@bestsss, tal vez mi mapa tiene un nombre pobre. No es un EnumMap, solo un mapa de String-> PresentationChannel (es decir, valor-> instancia) – Ray

Respuesta

5

Quería obtener algo de la sabia sabiduría de este grupo: ¿cuál es la forma correcta de indexar una enumeración por valor?

Muy posiblemente no hacerlo en absoluto.

Mientras tablas hash proporcionan O(1) de búsqueda, sino que también tienen una gran sobrecarga bastante constante (para los cálculos de hash, etc), así que para pequeñas colecciones una búsqueda lineal puede también ser más rápido (si "la manera eficiente" es su definición de "la forma apropiada").

Si lo que desea es una manera SECO hacerlo, supongo Guava Iterables.find es una alternativa:

return channelCode == null ? null : Iterables.find(Arrays.asList(values()), 
    new Predicate<PresentationChannel>() { 
     public boolean apply(PresentationChannel input) { 
      return input.getChannelCode().equals(channelCode); 
     } 
    }, null); 
+0

Buena llamada con respecto a "qué quiero decir con 'apropiado'". No es necesariamente el más eficiente. Si hay una sobrecarga innecesaria, eso no es bueno. Si es propenso a errores, tampoco es bueno. Creo que estaba pensando lo mismo que "¿Qué haría Josh Bloch Do?" – Ray

+0

Sí, esa es probablemente una definición bastante buena de "la forma correcta". – gustafc

+0

@Ray, Joshua explotó AbstarctCollection.toArray() y la nueva ArrayList (Colección) en java 1.4, desde entonces tiene algunos desacuerdos. – bestsss

0

Si usted está esperando la proporcionada channelCode estar siempre válida a continuación, puedes tratar de obtener la instancia correcta de la enumeración usando el método valueOf(). Si el valor proporcionado no es válido, puede devolver nulo o propagar la excepción.

try { 
    return PresentationChannel.valueOf(channelCode); 
catch (IllegalArgumentException e) { 
    //do something. 
} 
3

¿Por qué no nombrar a sus miembros A, B, C, D, E y utilizar valueOf?

+0

porque pueden ser así 1,2,3,4 o bien Николас1, Николас2, etc., es decir, no identificadores de Java. – bestsss

+1

Preferiría no tener los nombres tan estrechamente unidos a los valores. – Ray

5

Creo que estás usando clases que no son JDK aquí ¿verdad?

Una solución similar con API JDK:

private static final Map<String, PresentationChannel> channels = new HashMap<String, PresentationChannel>(); 

static{ 
    for (PresentationChannel channel : values()){ 
    channels.put(channel.getChannelCode(), channel); 
    } 
} 
+0

Sí, estoy usando Guava – Ray

2

para unos valores que está bien, la iteración a través de la matriz de valores(). Una sola nota: use smth así. values() clona la matriz en cada invocación.

static final PresentationChannel[] values=values(); 
static PresentationChannel getByCode(String code){ 
    if (code==null) 
    return null; 
    for(PresentationChannel channel: values) if (code.equals(channel.channelCode)) return channel; 
    return null; 
} 

si usted tiene más Canales.

private static final Map<String code, PresentationChannel> map = new HashMap<String code, PresentationChannel>(); 
static{//hashmap sucks a bit, esp if you have some collisions so you might need to initialize the hashmap depending on the values count and w/ some arbitrary load factor 
    for(PresentationChannel channel: values()) map.put(channel.channelCode, channel); 
} 

static PresentationChannel getByCode(String code){ 
    return map.get(code); 
} 

Editar:

Así implementar una interfaz de ayuda, como se muestra a continuación, otro ejemplo por qué los genéricos sintaxis de Java golpes ya veces mejor - no se utilizan.

Uso PresentationChannel channel = EnumRepository.get(PresentationChannel.class, "A");
Habrá gastos generales, pero bueno, es bastante infalible.

public interface Identifiable<T> { 
     T getId();  



    public static class EnumRepository{ 
     private static final ConcurrentMap<Class<? extends Identifiable<?>>, Map<?, ? extends Identifiable<?>>> classMap = new ConcurrentHashMap<Class<? extends Identifiable<?>>, Map<?,? extends Identifiable<?>>>(16, 0.75f, 1); 

     @SuppressWarnings("unchecked") 
     public static <ID, E extends Identifiable<ID>> E get(Class<E> clazz, ID value){ 
     Map<ID, E> map = (Map<ID, E>) classMap.get(clazz); 
     if (map==null){ 
      map=buildMap(clazz); 
      classMap.putIfAbsent(clazz, map);   
     } 
     return map.get(value); 
     } 

     private static <ID, E extends Identifiable<ID>> Map<ID, E> buildMap(Class<E> clazz){ 
     E[] enumConsts = clazz.getEnumConstants(); 
     if (enumConsts==null) 
      throw new IllegalArgumentException(clazz+ " is not enum"); 

     HashMap<ID, E> map = new HashMap<ID, E>(enumConsts.length*2); 
     for (E e : enumConsts){ 
      map.put(e.getId(), e); 
     } 
     return map; 
     }  
    } 
} 

enum X implements Identifiable<String>{ 
... 
public String getId(){...} 
} 

aviso menor: si se pone identificable en algún lugar, y muchos proyectos/wepapp dependen de ella (y compartirlo) y así sucesivamente, es posible filtrar las clases/cargadores de clases.

1

Aquí es otra manera de implementar un mapa inmodificable:

protected static final Map<String, ChannelCode> EnumMap; 
static { 
    Map<String, ChannelCode> tempMap = new HashMap<String, ChannelCode>(); 
    tempMap.put("A", ChannelA); 
    tempMap.put("B", ChannelB); 
    tempMap.put("C", ChannelC); 
    tempMap.put("D", ChannelD); 
    tempMap.put("E", ChannelE); 
    EnumMap = Collections.unmodifiableMap(tempMap); 
} 

Puede utilizar EnumMap.get(someCodeAthroughE) para recuperar rápidamente el ChannelCode. Si la expresión es nula, entonces no se encontró su someCodeAthroughE.

3

que estaba buscando algo similar y conocer en this site un simple, limpio y recto al punto de paso . Crear e inicializar un mapa final estática dentro de su enumeración y añadir un método estático para las operaciones de búsqueda, por lo que sería algo así como:

public enum PresentationChannel { 
    ChannelA("A"), 
    ChannelB("B"), 
    ChannelC("C"), 
    ChannelD("D"), 
    ChannelE("E"); 

    private String channelCode; 

    PresentationChannel(String channelCode) { 
     this.channelCode = channelCode; 
    } 

    public String getChannelCode() { 
     return this.channelCode; 
    } 

    private static final Map<String, PresentationChannel> lookup 
      = new HashMap<String, PresentationChannel>(); 

    static { 
     for(PresentationChannel pc : EnumSet.allOf(PresentationChannel.class)) { 
      lookup.put(pc.getChannelCode(), pc); 
     } 
    } 

    public static PresentationChannel get(String channelCode) { 
     return lookup.get(channelCode); 
    } 
} 
+1

En lugar de usar un 'EnumSet' puede usar simplemente' PresentationChannel.values ​​() ' – pimlottc

Cuestiones relacionadas