【问题标题】:Finding enum value with Java 8 Stream API使用 Java 8 Stream API 查找枚举值
【发布时间】:2015-03-04 15:01:31
【问题描述】:

假设有一个名为 Type 的简单枚举定义如下:

enum Type{
    X("S1"),
    Y("S2");

    private String s;

    private Type(String s) {
        this.s = s;
    }
}

为给定的s 找到正确的枚举可以使用带有 for 循环的静态方法轻松完成(假设该方法是在枚举中定义的),例如:

private static Type find(String val) {
        for (Type e : Type.values()) {
            if (e.s.equals(val))
                return e;
        }
        throw new IllegalStateException(String.format("Unsupported type %s.", val));
}

我认为用 Stream API 表达的功能等价物是这样的:

private static Type find(String val) {
     return Arrays.stream(Type.values())
            .filter(e -> e.s.equals(val))
            .reduce((t1, t2) -> t1)
            .orElseThrow(() -> {throw new IllegalStateException(String.format("Unsupported type %s.", val));});
}

我们怎样才能写得更好更简单?这段代码感觉是强制的,不是很清楚。 reduce() 尤其显得笨重和滥用,因为它不累积任何东西,不执行任何计算,并且总是简单地返回 t1(如果过滤器返回一个值 - 如果它不返回一个值,那显然是一场灾难),更不用说 @ 987654327@是不是有多余的和混乱的。然而,我在 Stream API 中找不到任何东西,它只是以某种方式从 Stream<T> 直接返回 T

有没有更好的办法?

【问题讨论】:

  • 我知道这条评论不会被任何人赞成,但与 Java 8 一样伟大的是,您不必为每个问题都使用Streams。您的 for 循环方法比使用 Streams 的任何方法都更清晰(更快)。
  • @pbabcdefp 好吧,我认为这是一个很好的评论,但是如果我赞成,那么您评论中的第一句话就是错误的,这意味着我必须再次反对,然后我会再次认为这是一个很好的评论,所以我不得不赞成它,但是第一句话是错误的......我想我要抛出StackOverflowException......
  • @pbabcdefp - 这可能是一个见仁见智的问题,但我发现 lambda 越来越优于迭代,而且清晰度几乎总是胜过效率。我很肯定我已经尝试过 findFirst() 并且在 IDEA 中遇到了一些奇怪的编译错误并编写了 reduce() 变体。无论如何,我对你所有的答案都投了赞成票,但觉得firstany 更清楚,所以我同意了。感谢您的帮助!

标签: java enums functional-programming java-8 java-stream


【解决方案1】:

我会改用findFirst

return Arrays.stream(Type.values())
            .filter(e -> e.s.equals(val))
            .findFirst()
            .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));


尽管在这种情况下 Map 可能会更好:
enum Type{
    X("S1"),
    Y("S2");

    private static class Holder {
        static Map<String, Type> MAP = new HashMap<>();
    }

    private Type(String s) {
        Holder.MAP.put(s, this);
    }

    public static Type find(String val) {
        Type t = Holder.MAP.get(val);
        if(t == null) {
            throw new IllegalStateException(String.format("Unsupported type %s.", val));
        }
        return t;
    }
}

我从这个answer 学到了这个技巧。基本上,类加载器在枚举类之前初始化静态类,这允许您在枚举构造函数本身中填充Map。很方便!

希望对您有所帮助! :)

【讨论】:

  • 这是一个非常漂亮的技巧,我特别喜欢 JVM 如何保证串行映射填充 - 非常好。只是一个小建议 - 我们可以通过删除字段 s 使代码更加紧凑,因为它在其他地方没有使用。
  • findAny() 而不是findFirst()? -- 如果你保证一个(或零个)匹配,那么findAny() 可能会更快得到答案(尽管我想一个枚举是否足够大以使搜索并行化是值得怀疑的)
  • @slim 随着流的排序,findFirst() 在多个匹配的情况下表现出与原始 pre-java 8 代码相同的行为。尽管我怀疑枚举的值和名称之间存在双射映射,但我预计findFirst()findAny() 之间的性能差异不会很大(正如您所说,这是有问题的)。也就是说,我会使用第二种方法。它使用更多的内存,但它可能是值得的,因为查找时间更好:)
  • 第二种方法在 Big O(1) 中运行,而所有其他方法将采用 BigO(n)...感谢您的回答
【解决方案2】:
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...);

【讨论】:

    【解决方案3】:

    findAny()代替reduce怎么样?

    private static Type find(String val) {
       return Arrays.stream(Type.values())
            .filter(e -> e.s.equals(val))
            .findAny()
            .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
    }
    

    【讨论】:

    • orElseThrow 需要一个 Supplier,顾名思义,它提供异常,而不是抛出异常,所以不是@ 987654326@ 你应该使用.orElseThrow(() -&gt; new IllegalStateException …)。使用受检异常时,您会注意到不同之处。
    • @Holger - 很好,已修复。谢谢!
    【解决方案4】:

    我还不能添加评论,所以我发布了一个答案来补充上述answer,只是遵循相同的想法,但使用 java 8 方法:

    public static Type find(String val) {
        return Optional
                .ofNullable(Holder.MAP.get(val))
                .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
    }
    

    【讨论】:

      【解决方案5】:

      接受的答案效果很好,但如果您想避免使用临时数组创建新流,您可以使用EnumSet.allOf()

      EnumSet.allOf(Type.class)
             .stream()
             .filter(e -> e.s.equals(val))
             .findFirst()
             .orElseThrow(String.format("Unsupported type %s.", val));
      

      【讨论】:

      • 查看 JDK 源码,Arrays.stream(Type.values()) 在内部克隆了一个数组,然后创建了一个新流 - 而EnumSet.allOf(Type.class).stream() 在内部创建了一个新的 EnumSet,将所有枚举值添加到其中,然后创建了一个新流.这个解决方案对我来说看起来更好,但使用它的决定不应该仅仅基于关于创建了多少对象的假设。
      • @kapex 因为 enun 是常量,所以你仍然会一直复制。另一方面,EnumSet 可以引入诸如 DISTINCTNONNULL 之类的标志,这些标志可以稍后被流利用,它不只是关于对象
      • @Eugene 对,EnumSet 可以提供优化流的提示,这将是使用它的更好理由。你是什​​么意思“因为枚举是常量,你仍然复制,一直。”?枚举常量定义枚举类型的枚举实例。据我所知,复制枚举数组的工作方式与复制任何其他对象类型的数组相同。
      • @kapex 我的意思是values 总是返回一个新数组,当然是通过复制,因为返回一个由原始数组支持的数组意味着有可能改变枚举本身并且不能显然会发生。
      • e.s.equals(val) 这里的 s 值是多少?
      【解决方案6】:

      你需要一个用于 String 的 getter。 在下面的例子中这个方法是getDesc():

      public static StatusManifestoType getFromValue(String value) {
          return Arrays.asList(values()).stream().filter(t -> t.getDesc().equals(value)).findAny().orElse(null);
      }
      

      【讨论】:

        【解决方案7】:

        我知道这个问题很老,但我是从重复而来的。我的回答并不是严格回答 OP 关于如何使用 Java Streams 解决问题的问题。相反,此答案扩展了accepted answer 中提出的基于Map 的解决方案,使其更易于管理(恕我直言)。

        就是这样:我建议引入一个特殊的辅助类,我将其命名为EnumLookup

        假设 Type 枚举写得稍微好一些(有意义的字段名 + getter),我向它注入一个 EnumLookup 常量,如下所示:

        enum Type {
        
            X("S1"),
            Y("S2");
        
            private static final EnumLookup<Type, String> BY_CODE = EnumLookup.of(Type.class, Type::getCode, "code");
        
            private final String code;
        
            Type(String code) {
                this.code = code;
            }
        
            public String getCode() {
                return code;
            }
        
            public static EnumLookup<Type, String> byCode() {
                return BY_CODE;
            }
        }
        

        然后用法变得(再次,IMO)真正可读:

        Type type = Type.byCode().get("S1"); // returns Type.X
        
        Optional<Type> optionalType = Type.byCode().find("S2"); // returns Optional(Type.Y)
        
        if (Type.byCode().contains("S3")) { // returns false
            // logic
        }
        

        最后,这是EnumLookup帮助类的代码:

        public final class EnumLookup<E extends Enum<E>, ID> {
        
            private final Class<E> enumClass;
            private final ImmutableMap<ID, E> valueByIdMap;
            private final String idTypeName;
        
            private EnumLookup(Class<E> enumClass, ImmutableMap<ID, E> valueByIdMap, String idTypeName) {
                this.enumClass = enumClass;
                this.valueByIdMap = valueByIdMap;
                this.idTypeName = idTypeName;
            }
        
            public boolean contains(ID id) {
                return valueByIdMap.containsKey(id);
            }
        
            public E get(ID id) {
                E value = valueByIdMap.get(id);
                if (value == null) {
                    throw new IllegalArgumentException(String.format(
                            "No such %s with %s: %s", enumClass.getSimpleName(), idTypeName, id
                    ));
                }
                return value;
            }
        
            public Optional<E> find(ID id) {
                return Optional.ofNullable(valueByIdMap.get(id));
            }
        
            //region CONSTRUCTION
            public static <E extends Enum<E>, ID> EnumLookup<E, ID> of(
                    Class<E> enumClass, Function<E, ID> idExtractor, String idTypeName) {
                ImmutableMap<ID, E> valueByIdMap = Arrays.stream(enumClass.getEnumConstants())
                        .collect(ImmutableMap.toImmutableMap(idExtractor, Function.identity()));
                return new EnumLookup<>(enumClass, valueByIdMap, idTypeName);
            }
        
            public static <E extends Enum<E>> EnumLookup<E, String> byName(Class<E> enumClass) {
                return of(enumClass, Enum::name, "enum name");
            }
            //endregion
        }
        

        注意:

        1. 我在这里使用了 Guava 的 ImmutableMap,但可以使用普通的 HashMapLinkedHashMap

        2. 1234563 )

        【讨论】:

          【解决方案8】:

          我认为 Alexis C. (Alexis C.'s answer) 的第二个答案在复杂性方面是一个不错的答案。而不是每次使用

          查找代码时都在 O(n) 中进行搜索
          return Arrays.stream(Type.values())
                  .filter(e -> e.s.equals(val))
                  .findFirst()
                  .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val)));
          

          您可以在加载类时使用 O(n) 时间,方法是将所有元素放入映射中,然后使用映射在恒定时间 O(1) 内访问该类型的代码。

          enum Type{
          X("S1"),
          Y("S2");
          
          private final String code;
          private static Map<String, Type> mapping = new HashMap<>();
          
          static {
              Arrays.stream(Type.values()).forEach(type-> mapping.put(type.getCode(), type));
          }
          
          Type(String code) {
              this.code = code;
          }
          
          public String getCode() {
              return code;
          }
          
          public static Type forCode(final String code) {
              return mapping.get(code);
          }
          }
          

          【讨论】:

            【解决方案9】:

            你需要一个用于 String 的 getter,但这是我使用的模式:

            private static final Map<String, Type> TYPE_MAP = 
                Collections.unmodifiableMap(
                    EnumSet.allOf(Type.class)
                    .stream()
                    .collect(Collectors.toMap(Type::getS, e -> e)));
            
            public static Type find(String s) {
                return TYPE_MAP.get(s);
            }
            

            没有 for 循环,只有流。与每次调用方法时都构建流相反,快速查找。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2020-07-30
              • 1970-01-01
              • 2017-02-12
              • 1970-01-01
              • 2010-11-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多