2011-02-03 3 views
11

У меня есть несколько перечислений Java, которые выглядят как-то вроде ниже (отредактированы для обеспечения конфиденциальности и т. Д.). В каждом случае у меня есть метод поиска, который я действительно не удовлетворен; в приведенном ниже примере это findByChannelCode.Правильный способ поиска перечисления по значению

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

Проблема заключается в том, я чувствую себя глупо делать эту линейную Lookups, когда я мог бы просто использовать в HashMap<String, PresentationChannel>. Поэтому я подумал о решении ниже, но это немного грязнее, что я надеюсь, и, более того, я не хотел изобретать колесо, когда кто-то еще сталкивался с этим. Я хотел получить часть мудрости мудреца этой группы: Каков правильный способ индексирования перечисления по значению?

Мое решение:

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

и, в перечислении:

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

вы понимаете, что EnumMap карта enum-> Объект не вице-стиха? – bestsss

+0

Профилирование кода показывает, что линейный поиск неадекватен? – trashgod

+0

@ bestsss, возможно, моя карта плохо названа. Это не EnumMap, просто карта из String-> PresentationChannel (то есть value-> instance) – Ray

ответ

5

Я хотел получить часть мудрости мудрецов этой группы: Каков правильный способ индексирования перечисления по значению?

Вполне возможно не делает это вообще.

В то время как хэш-таблицы обеспечивают поиск O(1), они также имеют довольно большие постоянные накладные расходы (для расчетов хэшей и т. Д.), Поэтому для небольших коллекций линейный поиск может быть быстрее (если «эффективным способом» является ваше определение « надлежащим образом").

Если вы просто хотите DRY способ сделать это, я полагаю, гуавы-х Iterables.find альтернатива:

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

Хороший отзыв о «что я подразумеваю под« правильным ». Это не обязательно самый эффективный. Если нет лишних накладных расходов, это не хорошо. Если это склонно к ошибкам, это тоже плохо. Думаю, я размышлял так: «Что сделает Джош Блох?» – Ray

+0

Да, это, наверное, довольно хорошее определение «правильный путь». – gustafc

+0

@Ray, Джошуа взорвал AbstarctCollection.toArray() и новый ArrayList (Collection) в java 1.4, с тех пор есть некоторые разногласия. – bestsss

0

Если вы ожидаете прилагаемую channelCode, чтобы всегда быть в силе, то вы можете просто попытаться получить правильный экземпляр перечисление с использованием метода valueOf(). Если предоставленное значение недействительно, вы можете вернуть null или распространить исключение.

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

Почему вы не называете ваши члены A, B, C, D, E и использовать valueOf?

+0

, потому что они могут быть похожими на 1,2,3,4 или Николас1, Николас2 и т. Д., То есть не на java-идентификаторы. – bestsss

+1

Я бы предпочел, чтобы имена не были так тесно связаны со значениями. – Ray

5

Думаю, вы используете здесь классы, отличные от JDK?

Аналогичное решение с JDK API:

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

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

Да, я использую Guava – Ray

2

для нескольких значений, которые в порядке, итерации через массив значений(). Только одно примечание: используйте smth. values() клонирует массив по каждому вызову.

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

если у вас больше каналов.

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:

Так реализовать вспомогательный интерфейс, как показано ниже, другой пример, почему синтаксис Java дженериков удары, а иногда - лучше не использовать.

Использование PresentationChannel channel = EnumRepository.get(PresentationChannel.class, "A");
Будет накладные расходы, но хорошо, это довольно глупое доказательство.

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

Minor предупреждение: если вы поставите идентифицируемых где-то там, и многие проекты/wepapp зависят от него (и делиться ею) и так далее, можно просочиться классов/загрузчики классов.

1

Вот еще один способ реализации неизменяемых карт:

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

Вы можете использовать EnumMap.get(someCodeAthroughE) быстро извлечь ChannelCode. Если выражение равно null, ваш someCodeAthroughE не найден.

3

Я искал что-то подобное и нашел на this site простой, чистой и прямой до точки пути , Создать и инициализировать статическую конечную карту внутри вашего перечисления и добавить статический метод для поиска, так что это будет что-то вроде:

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

Вместо использования 'EnumSet' вы можете использовать только' PresentationChannel.values ​​() ' – pimlottc