如何在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()
而不是三个argcollect()
函数?”问题是我们需要创建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}}