如何在Java';兰姆达API

如何在Java';兰姆达API,java,lambda,java-8,java-stream,enum-map,Java,Lambda,Java 8,Java Stream,Enum Map,从将一对Enums映射为一个值的java.util.function.BiFunction中,我想构建一个反映该映射的EnumMap 例如,让E1和E2成为enum类型和T任何给定类型: BiFunction<E1,E2, T> theBiFunction = //...anything EnumMap<E1,EnumMap<E2,T>> theMap = buildTheMap( // <--

从将一对
Enum
s映射为一个值的
java.util.function.BiFunction
中,我想构建一个反映该映射的
EnumMap

例如,让
E1
E2
成为
enum
类型和
T
任何给定类型:

 BiFunction<E1,E2, T> theBiFunction = //...anything

 EnumMap<E1,EnumMap<E2,T>> theMap = 
    buildTheMap(                     // <--  this is where the magic happens
                E1.values(), 
                E2.values(),
                theBiFunction);
以下两个值应相等:

T valueFromTheMaps = theMap.get(e1).get(e2);
T valueFromTheFunction = theBiFunction.apply(e1,e2);

boolean alwaysTrue = valueFromTheMaps.equals(valueFromTheFunction);

发生“魔法”的方法的最佳(更优雅、更高效等)实现是什么?

作为比较基准,以下是常规版本:

<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
                                          E2[] e2values,
                                          BiFunction<E1,E2,T> f) {
    EnumMap<E1,EnumMap<E2,T>> outer = new EnumMap<>(E1.class);
    for (E1 e1 : e1values) {
        EnumMap<E2,T> inner = new EnumMap<>(E2.class);
        for (E2 e2 : e2values) {
            inner.put(e2, f.apply(e1, e2));
        }
        outer.put(e1, inner);
    }
    return outer;
}
让这个问题变得麻烦的是,外部收集器的累加器函数必须运行一个具有自己的三个arg收集器的流来生成内部映射。这确实很难很好地缩进。我没有使用标准间距,而是为每个
collect()
调用排列了三个参数。这使得它相当宽,但如果我不这样做,它将很难看到什么与什么,因为筑巢是如此之深。虽然我是streams的粉丝,但很难说这比传统版本好

您可能会说,“为什么不使用
toMap()
而不是三个arg
collect()
函数?”问题是我们需要创建
EnumMap
实例,而采用映射供应商的
toMap()
重载有四个参数:

toMap(keyFunc, valueFunc, mergeFunc, mapSupplier)
更糟糕的是,没有使用合并函数(第三个参数),因此我们必须提供一个从未使用过的函数。下面是它的样子:

<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
                                          E2[] e2values,
                                          BiFunction<E1,E2,T> f) {
    return
        Stream.of(e1values)
              .collect(toMap(e1 -> e1,
                             e1 -> Stream.of(e2values)
                                         .collect(toMap(e2 -> e2,
                                                        e2 -> f.apply(e1, e2),
                                                        (x, y) -> x,
                                                        () -> new EnumMap<>(E2.class))),
                             (x, y) -> x,
                             () -> new EnumMap<>(E1.class)));
}
EnumMap buildTheMap(E1[]E1值,
E2[]E2值,
双函数(f){
返回
流量(e1值)
.收集(toMap)(e1->e1,
e1->流(E2值)
.收集(toMap(e2->e2),
e2->f.apply(e1,e2),
(x,y)->x,
()->新的EnumMap(E2.class)),
(x,y)->x,
()->新的枚举映射(E1.class));
}
在我看来没有任何好转。我的钱仍在传统版本上


有许多可供选择的方法可以尝试。我们来看看睡个好觉能带来什么。

如果你找到一个通用的解决方案并将其分解,你会得到一个优雅的解决方案。首先,实现一个通用函数,该函数在
函数
的基础上创建一个
枚举映射
,然后使用第一个函数结合自身实现
双函数
的嵌套映射:

static <T,E extends Enum<E>>
  EnumMap<E,T> funcToMap(Function<E,T> f, Class<E> t, E... values) {
    return Stream.of(values)
      .collect(Collectors.toMap(Function.identity(), f, (x,y)->x, ()-> new EnumMap<>(t)));
}
static <T,E1 extends Enum<E1>,E2 extends Enum<E2>>
  EnumMap<E1,EnumMap<E2,T>> biFuncToMap(
  BiFunction<E1,E2,T> f, Class<E1> t1, Class<E2> t2, E1[] values1, E2[] values2){

  return funcToMap(e1->funcToMap(e2->f.apply(e1, e2), t2, values2), t1, values1);
}

当然,使用通用解决方案,您可以为不需要
Class
参数的具体
enum
类型构建方法



如果所提供的
(Bi)函数是线程安全的,那么它应该能够在并行流中顺利工作。

您的不雅观、低效的版本是什么样子的?两个嵌套的foreach将内容添加到映射中:(…这有什么不对?例如,请参阅下面@stuart marks的答案,以了解如何在不同的方法下解决这个问题。通过关注所有lambda,您忘记了让复杂代码更简单的好方法:将其拆分为多个方法。@Holger我没有忘记,只是不清楚而已对我来说,这确实会改善情况。我有点困了。:-)但谢谢你的贡献(+1)。总的来说,我确实同意提取小方法是一种很好的技术。不幸的是,从一开始,在地图中收集数据流有一定的笨拙。此外,这个问题需要嵌套映射,再加上需要构造
EnumMap
使事情变得更糟。我仍然认为有更简单的解决方案在努力摆脱困境。特别是如果我们愿意考虑API的变化。API的变化为时已晚吗?@ java 8的霍格尔,是的,已经太迟了。但是有Java9。考虑这一点似乎还为时过早,但现在人们正在用Java8进行尝试,找出哪些不好用的东西可以用来将API更改(当然是兼容的)驱动到Java9中。非常“优雅”。如果允许的话,可以通过删除funcToMap的最后一个参数(
E…values
)并使用
E[]values=E.getEnumConstants()来改进它
在方法体中。@bruno:实际上,您在问题中已经将
enum
值传递到了factory方法中,因此我假设这是一项要求,即能够通过参数控制
映射中存在的键。如果您想拥有所有常量,我建议使用
getEnumConstants()
添加另一个方法,将其委托给此方法。这避免了在嵌套用法中多次调用
getEnumConstants()
。顺便说一句,我没有忘记
收集器。
,我只是假设您将使用
静态导入
。哦,我忘记了
静态导入
<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
                                          E2[] e2values,
                                          BiFunction<E1,E2,T> f) {
    return
        Stream.of(e1values)
              .collect(toMap(e1 -> e1,
                             e1 -> Stream.of(e2values)
                                         .collect(toMap(e2 -> e2,
                                                        e2 -> f.apply(e1, e2),
                                                        (x, y) -> x,
                                                        () -> new EnumMap<>(E2.class))),
                             (x, y) -> x,
                             () -> new EnumMap<>(E1.class)));
}
static <T,E extends Enum<E>>
  EnumMap<E,T> funcToMap(Function<E,T> f, Class<E> t, E... values) {
    return Stream.of(values)
      .collect(Collectors.toMap(Function.identity(), f, (x,y)->x, ()-> new EnumMap<>(t)));
}
static <T,E1 extends Enum<E1>,E2 extends Enum<E2>>
  EnumMap<E1,EnumMap<E2,T>> biFuncToMap(
  BiFunction<E1,E2,T> f, Class<E1> t1, Class<E2> t2, E1[] values1, E2[] values2){

  return funcToMap(e1->funcToMap(e2->f.apply(e1, e2), t2, values2), t1, values1);
}
enum Fruit {
    APPLE, PEAR
}
enum Color {
    RED, GREED, YELLOW
}
EnumMap<Fruit, EnumMap<Color, String>> result
  =biFuncToMap((a,b)->b+" "+a,
     Fruit.class, Color.class, Fruit.values(), Color.values());
System.out.println(result);
{APPLE={RED=RED APPLE, GREED=GREED APPLE, YELLOW=YELLOW APPLE}, PEAR={RED=RED PEAR, GREED=GREED PEAR, YELLOW=YELLOW PEAR}}