Правильный способ поиска перечисления по значению - PullRequest
11 голосов
/ 03 февраля 2011

У меня есть несколько перечислений 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;
    }
}

Проблема в том, что я чувствую себя глупо, выполняя эти линейные поиски, когда могу просто использовать 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);
}

Ответы [ 7 ]

5 голосов
/ 03 февраля 2011

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

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

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

Если вы просто хотите СУХОЙ способ сделать это, я думаю, что 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);
5 голосов
/ 03 февраля 2011

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

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

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

static{
  for (PresentationChannel channel : values()){
    channels.put(channel.getChannelCode(), channel);
  }
}
3 голосов
/ 26 марта 2013

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

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); 
    }
}
3 голосов
/ 03 февраля 2011

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

2 голосов
/ 03 февраля 2011

для нескольких значений это нормально, итерация по массиву значений (). Только одна заметка: используйте что-то подобное. 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(){...}
}

Небольшое предупреждение: если вы поместите Identifiable где-то там, и многие проекты / wepapp зависят от него (и поделятся им) и так далее, возможно утечка классов / загрузчиков классов.

1 голос
/ 03 февраля 2011

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

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. Если выражение равно нулю, то ваш someCodeAthroughE не был найден.

0 голосов
/ 03 февраля 2011

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

try {
    return PresentationChannel.valueOf(channelCode);
catch (IllegalArgumentException e) {
    //do something.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...