giu*_*eri 4 java java-8 java-stream
考虑以下代码:
Function<BigDecimal,BigDecimal> func1 = x -> x;//This could be anything
Function<BigDecimal,BigDecimal> func2 = y -> y;//This could be anything
Map<Integer,BigDecimal> data = new HashMap<>();
Map<Integer,BigDecimal> newData =
data.entrySet().stream().
collect(Collectors.toMap(Entry::getKey,i ->
func1.apply(i.getValue())));
List<BigDecimal> list =
newData.entrySet().stream().map(i ->
func2.apply(i.getValue())).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
基本上我正在做的是使用func1更新HashMap,使用func2应用第二次转换并将第二次更新的值保存在列表中。我以一成不变的方式DID生成新对象newData和list。
我的问题:是否可以将原始HashMap(数据)流一次?
我尝试了这个:
Function<BigDecimal,BigDecimal> func1 = x -> x;
Function<BigDecimal,BigDecimal> func2 = y -> y;
Map<Integer,BigDecimal> data = new HashMap<>();
List<BigDecimal> list = new ArrayList<>();
Map<Integer,BigDecimal> newData =
data.entrySet().stream().collect(Collectors.toMap(
Entry::getKey,i ->
{
BigDecimal newValue = func1.apply(i.getValue());
//SIDE EFFECT!!!!!!!
list.add(func2.apply(newValue));
return newValue;
}));
Run Code Online (Sandbox Code Playgroud)
但是这样做会在列表更新中产生副作用,因此我失去了“不变方式”的要求。
这似乎是Collectors.teeingJDK 12中即将推出的方法的理想用例。这是webrev,这是CSR。您可以按以下方式使用它:
Map.Entry<Map<Integer, BigDecimal>, List<BigDecimal>> result = data.entrySet().stream()
.collect(Collectors.teeing(
Collectors.toMap(
Map.Entry::getKey,
i -> func1.apply(i.getValue())),
Collectors.mapping(
i -> func1.andThen(func2).apply(i.getValue()),
Collectors.toList()),
Map::entry));
Run Code Online (Sandbox Code Playgroud)
Collectors.teeing收集到两个不同的收集器,然后将两个部分结果合并为最终结果。对于最后一步,我使用的是JDK 9的Map.entry(K k, V v)静态方法,但是我可以使用任何其他容器,例如Pair或Tuple2,等等。
对于第一集我用你的确切代码,以收集到Map,而第二集我使用的Collectors.mapping一起Collectors.toList使用Function.andThen来编写func1和func2函数映射步骤。
编辑:如果您不能等到JDK 12发布,您可以同时使用此代码:
public static <T, A1, A2, R1, R2, R> Collector<T, ?, R> teeing(
Collector<? super T, A1, R1> downstream1,
Collector<? super T, A2, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
class Acc {
A1 acc1 = downstream1.supplier().get();
A2 acc2 = downstream2.supplier().get();
void accumulate(T t) {
downstream1.accumulator().accept(acc1, t);
downstream2.accumulator().accept(acc2, t);
}
Acc combine(Acc other) {
acc1 = downstream1.combiner().apply(acc1, other.acc1);
acc2 = downstream2.combiner().apply(acc2, other.acc2);
return this;
}
R applyMerger() {
R1 r1 = downstream1.finisher().apply(acc1);
R2 r2 = downstream2.finisher().apply(acc2);
return merger.apply(r1, r2);
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::applyMerger);
}
Run Code Online (Sandbox Code Playgroud)
注意:创建返回的收集器时,不考虑下游收集器的特性(作为练习)。
编辑2:即使使用两个流,您的解决方案也完全可以。我上面的解决方案只对原始地图进行一次流式处理,但func1对所有值都应用两次。如果func1价格昂贵,则可以考虑对其进行记忆(即缓存其结果,以便每当使用相同的输入再次调用它时,都从缓存中返回结果,而不是再次对其进行计算)。或者,您也可以先应用func1原始地图的值,然后使用进行收集Collectors.teeing。
记忆很容易。只需声明此实用程序方法:
public <T, R> Function<T, R> memoize(Function<T, R> f) {
Map<T, R> cache = new HashMap<>(); // or ConcurrentHashMap
return k -> cache.computeIfAbsent(k, f);
}
Run Code Online (Sandbox Code Playgroud)
然后按如下方式使用它:
Function<BigDecimal, BigDecimal> func1 = memoize(x -> x); //This could be anything
Run Code Online (Sandbox Code Playgroud)
现在,您可以使用该备注func1,它可以像以前一样工作,除了apply使用先前使用过的参数调用其方法时,它将从缓存中返回结果。
另一种解决方案是先申请func1然后收集:
Map.Entry<Map<Integer, BigDecimal>, List<BigDecimal>> result = data.entrySet().stream()
.map(i -> Map.entry(i.getKey(), func1.apply(i.getValue())))
.collect(Collectors.teeing(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue),
Collectors.mapping(
i -> func2.apply(i.getValue()),
Collectors.toList()),
Map::entry));
Run Code Online (Sandbox Code Playgroud)
同样,我正在使用jdk9的Map.entry(K k, V v)静态方法。
| 归档时间: |
|
| 查看次数: |
204 次 |
| 最近记录: |