2011-02-03 9 views
11

Ho diverse enumerazioni Java simili a quelle riportate di seguito (modificate per riservatezza, ecc.). In ogni caso, ho un metodo di ricerca che non sono davvero soddisfatto; nell'esempio seguente, è findByChannelCode.Il modo corretto per cercare un enum in base al valore

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; 
    } 
} 

Il problema è, mi sento stupida a fare queste ricerche lineari quando ho potuto solo utilizzare un HashMap<String, PresentationChannel>. Così ho pensato alla soluzione qui sotto, ma è un po 'più disordinata che mi piacerebbe sperare e, più precisamente, non mi importava di re-inventare la ruota quando sicuramente qualcun altro ha scoperto questo. Volevo ottenere un po 'del saggio saggio di questo gruppo: qual è il modo corretto di indicizzare un enum in base al valore?

La mia soluzione:

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

e, nel enum:

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

si capisce che le mappe enum-> EnumMap non oggetto viceversa? – bestsss

+0

Il profiling del codice mostra che la ricerca lineare è inadeguata? – trashgod

+0

@bestsss, forse la mia mappa è mal chiamata. Non è un EnumMap, solo una mappa da String-> PresentationChannel (vale a dire-> istanza) – Ray

risposta

5

Volevo ottenere un po 'di saggezza di questo gruppo: qual è il modo corretto di indicizzare un enum in base al valore?

Molto probabilmente non farlo affatto.

Mentre le tabelle hash forniscono la ricerca O(1), hanno anche un overhead costante piuttosto grande (per calcoli hash, ecc.), Quindi per le raccolte di piccole dimensioni una ricerca lineare potrebbe essere più veloce (se "la via efficiente" è la definizione di "la maniera giusta").

Se si desidera solo un modo DRY per farlo, credo di Guava Iterables.find è un'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

Buona callout riguardante "cosa intendo per 'corretto'". Non è necessariamente il più efficiente. Se c'è un sovraccarico non necessario, non va bene. Se è soggetto a errori, non va bene neanche. Credo che stavo pensando sulla falsariga di "What Would Josh Bloch Do?" – Ray

+0

Sì, probabilmente è una buona definizione di "modo corretto". – gustafc

+0

@Ray, Joshua ha fatto saltare in aria AbstarctCollection.toArray() e la nuova ArrayList (raccolta) in java 1.4, da allora hanno alcuni disaccordi. – bestsss

0

Se vi aspettate il previsto channelCode di essere sempre valida, allora si può solo cercare di ottenere la giusta istanza di l'enumerazione usando il metodo valueOf(). Se il valore fornito non è valido, è possibile restituire null o propagare l'eccezione.

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

Perché non nominare i membri A, B, C, D, E e utilizzare valueOf?

+0

perché possono essere come quello 1,2,3,4 o anche Николас1, Николас2 e così via, cioè non identificatori java. – bestsss

+1

Preferirei che i nomi non siano così strettamente accoppiati ai valori. – Ray

5

Penso che stai usando classi non JDK qui giusto?

Una soluzione simile con JDK API:

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

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

Sì, sto usando Guava – Ray

2

per alcuni valori che è ok, iterazione attraverso l'array valori(). Una sola nota: usa smth in questo modo. values() clona l'array su ogni chiamata.

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; 
} 

se si dispone di più canali.

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); 
} 

Edit:

Quindi implementare un'interfaccia aiutante, come mostrato di seguito, un altro esempio perché i generici sintassi java colpi e qualche volta - meglio non utilizzati.

Uso PresentationChannel channel = EnumRepository.get(PresentationChannel.class, "A");
Ci saranno in testa, ma bene, è abbastanza infallibile.

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(){...} 
} 

avvertimento minore: se si mette identificabili qualche parte là fuori, e molti progetti/wepapp dipendono da esso (e condividere) e così via, è possibile perdere classi/classloader.

1

Ecco un altro modo per implementare una mappa immodificabile:

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); 
} 

È possibile utilizzare EnumMap.get(someCodeAthroughE) per recuperare rapidamente il ChannelCode. Se l'espressione è nullo, il tuo someCodeAthroughE non è stato trovato.

3

ero alla ricerca di qualcosa di simile e trovato su this site un semplice, pulito e dritto al punto di via . Creare e inizializzare una mappa finale statica all'interno del vostro enum e aggiungere un metodo statico per la ricerca, in modo che sarebbe qualcosa di simile:

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

Invece di usare un 'EnumSet' potresti usare solo' PresentationChannel.values ​​() ' – pimlottc

Problemi correlati