【问题标题】:The proper way to look up an enum by value按值查找枚举的正确方法
【发布时间】:2011-02-03 13:54:53
【问题描述】:

我有几个 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);
}

【问题讨论】:

  • 你明白 enumMap 映射 enum->Object 不是反之亦然吗?
  • 代码分析是否显示线性查找不足?
  • @bestsss,也许我的地图名字不好。它不是 EnumMap,只是来自 String->PresentationChannel 的映射(即 value->instance)
  • @trashgod,在这里的例子中,为了清楚起见,只有几个例子。在实际代码中,这种习语的例子还不少,而且查找次数也很多。
  • 如果您不需要自定义但通用的解决方案...我编辑答案让您退出,基本上您需要一个外部存储库和一个接口来实现。

标签: java enums


【解决方案1】:

我认为您在这里使用的是非 JDK 类,对吗?

使用 JDK API 的类似解决方案:

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

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

【讨论】:

    【解决方案2】:

    我想了解这个小组的一些睿智:按值索引枚举的正确方法是什么?

    很可能根本不这样做

    虽然哈希表提供O(1) 查找,但它们也有相当大的常量开销(用于哈希计算等),因此对于小型集合,线性搜索可能会更快(如果“有效方式”是您对“正确的方法”)。

    如果你只是想要一个 DRY 的方式来做,我想 Guava 的 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);
    

    【讨论】:

    • 关于“我所说的'适当'是什么意思”的良好呼吁。它不一定是最有效的。如果有不必要的开销,那就不好了。如果它容易出错,那也不好。我想我在想“乔什·布洛赫会做什么?”
    • 是的,这可能是对“正确方式”的一个很好的定义。
    • @Ray, Joshua 在 java 1.4 中炸毁了 AbstarctCollection.toArray() 和 new ArrayList(Collection),此后产生了一些分歧。
    • @gustafc 我想提出一个更正/建议 - EnumMap 是一个非常高效的地图。不需要散列,因为它可以使用带有内部索引的数组作为枚举值。
    • @pope 确实如此,但它们仅在 keys 为枚举时才有效,此处并非如此。
    【解决方案3】:

    您为什么不将您的成员命名为A, B, C, D, E 并使用valueOf

    【讨论】:

    • 因为它们也可以是 1、2、3、4 或 Николас1、Николас2 等,即不是 java 标识符。
    • 我不希望名称与值如此紧密地耦合。
    【解决方案4】:

    我正在寻找类似的东西,并在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); 
        }
    }
    

    【讨论】:

    • 您可以只使用EnumSet 而不是PresentationChannel.values()
    【解决方案5】:

    对于几个没问题的值,遍历值数组()。只有一个注意事项:像那样使用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);
    }
    

    编辑:

    所以实现一个辅助接口,如下所示,另一个例子说明为什么 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 依赖它(并共享它)等等,则可能会泄漏类/类加载器。

    【讨论】:

      【解决方案6】:

      这是实现不可修改地图的另一种方法:

      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

      【讨论】:

        【解决方案7】:

        如果您希望提供的 channelCode 始终有效,那么您可以尝试使用 valueOf() 方法获取正确的枚举实例。如果提供的值无效,您可以返回 null 或传播异常。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-06-18
          • 1970-01-01
          相关资源
          最近更新 更多