自定义地图收集器
Custom Map Collector
我有一个 collection 由 Map<Pair<DateTime, String>, List<Entity>>
组成,之前是使用流分组的。 Entity
是一个简单的 class 与 int
属性 和 getValue()
方法。
现在,我想通过使用我的简单 EntityAccumulator
将上一个映射的类型修改为 Map<Pair<DateTime, String>, EntityAccumulator>
来聚合 Entity
的值。据我所知,实现这一目标的唯一方法是创建自己的自定义收集器,但是我一直坚持使用 finisher()
方法,该方法应该 return Pair
.
或者,也许有更简单的方法来达到我想要的结果?
流处理
Map<Pair<DateTime, String>, EntityAccumulator> collect = entities.stream()
.collect(Collectors.groupingBy(entity-> Pair.of(entity.getTimestamp(), entity.getName())))
.entrySet().stream()
.collect(new EntityCollector()));
实体累加器
private static class EntityAccumulator {
private int result = 0.0;
public EntityAccumulator() { }
public EntityAccumulator(int result) {
this.result = result;
}
public void calculate(Entity entity) {
result += entity.getValue();
}
public EntityAccumulatoradd(EntityAccumulator other) {
return new EntityAccumulator(this.result + other.result);
}
}
收藏家
public class EntityCollector implements Collector<Map.Entry<Pair<DateTime, String>, List<Entity>>, EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> {
@Override
public Supplier<EntityAccumulator> supplier() {
return EntityAccumulator::new;
}
@Override
public BiConsumer<EntityAccumulator, Map.Entry<Pair<DateTime, String>, List<Entity>>> accumulator() {
return (result, pairListEntry) -> pairListEntry.getValue().forEach(result::calculate);
}
@Override
public BinaryOperator<EntityAccumulator> combiner() {
return EntityAccumulator::add;
}
@Override
public Function<EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> finisher() {
return (k) -> {
return null; // ??? HELP HERE
}
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
}
看来,你真的想做
Map<Pair<DateTime, String>, Double> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collectors.summingDouble(Entity::getValue)));
或
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collectors.summingInt(Entity::getValue)));
取决于实际值类型。你的声明 int result = 0.0
不是很清楚。
首先,如果你想对组进行归约,你应该为值提供 Collector
作为 groupingBy
collector 的第二个参数。然后,它不必处理 Map
和 Map.Entry
.
因为它基本上是将实体折叠成一个数字(对于每个组),您可以使用现有的收集器,即 summingInt
or summingDouble
.
当您创建自己的收集器时,您无法在整理器函数中重新构造您在累加器函数中删除的信息。如果您的容器类型 EntityAccumulator
仅包含一个数字,则无法从中生成 Map.Entry<Pair<DateTime, String>, EntityAccumulator>
。
顺便说一句,即使在创建自定义收集器时,您也很少需要使用 class 实现 Collector
接口。您可以简单地使用 Collector.of
,指定功能和特性,创建一个 Collector
.
所以使用你原来的 EntityAccumulator
class(假设 result
应该是 int
而 0.0
是一个错字),你可以使用
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collector.of(EntityAccumulator::new,
EntityAccumulator::calculate,
EntityAccumulator::add,
ea -> ea.result,
Collector.Characteristics.UNORDERED)));
实现同上。也可以分两步执行操作,就像您尝试的那样,使用
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(e -> Pair.of(e.getTimestamp(), e.getName())))
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().collect(
Collector.of(EntityAccumulator::new,
EntityAccumulator::calculate,
EntityAccumulator::add,
ea -> ea.result,
Collector.Characteristics.UNORDERED))));
但是,当然,这只是为了完整性。本回答开头的方案更简单高效。
我有一个 collection 由 Map<Pair<DateTime, String>, List<Entity>>
组成,之前是使用流分组的。 Entity
是一个简单的 class 与 int
属性 和 getValue()
方法。
现在,我想通过使用我的简单 EntityAccumulator
将上一个映射的类型修改为 Map<Pair<DateTime, String>, EntityAccumulator>
来聚合 Entity
的值。据我所知,实现这一目标的唯一方法是创建自己的自定义收集器,但是我一直坚持使用 finisher()
方法,该方法应该 return Pair
.
或者,也许有更简单的方法来达到我想要的结果?
流处理
Map<Pair<DateTime, String>, EntityAccumulator> collect = entities.stream()
.collect(Collectors.groupingBy(entity-> Pair.of(entity.getTimestamp(), entity.getName())))
.entrySet().stream()
.collect(new EntityCollector()));
实体累加器
private static class EntityAccumulator {
private int result = 0.0;
public EntityAccumulator() { }
public EntityAccumulator(int result) {
this.result = result;
}
public void calculate(Entity entity) {
result += entity.getValue();
}
public EntityAccumulatoradd(EntityAccumulator other) {
return new EntityAccumulator(this.result + other.result);
}
}
收藏家
public class EntityCollector implements Collector<Map.Entry<Pair<DateTime, String>, List<Entity>>, EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> {
@Override
public Supplier<EntityAccumulator> supplier() {
return EntityAccumulator::new;
}
@Override
public BiConsumer<EntityAccumulator, Map.Entry<Pair<DateTime, String>, List<Entity>>> accumulator() {
return (result, pairListEntry) -> pairListEntry.getValue().forEach(result::calculate);
}
@Override
public BinaryOperator<EntityAccumulator> combiner() {
return EntityAccumulator::add;
}
@Override
public Function<EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> finisher() {
return (k) -> {
return null; // ??? HELP HERE
}
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
}
看来,你真的想做
Map<Pair<DateTime, String>, Double> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collectors.summingDouble(Entity::getValue)));
或
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collectors.summingInt(Entity::getValue)));
取决于实际值类型。你的声明 int result = 0.0
不是很清楚。
首先,如果你想对组进行归约,你应该为值提供 Collector
作为 groupingBy
collector 的第二个参数。然后,它不必处理 Map
和 Map.Entry
.
因为它基本上是将实体折叠成一个数字(对于每个组),您可以使用现有的收集器,即 summingInt
or summingDouble
.
当您创建自己的收集器时,您无法在整理器函数中重新构造您在累加器函数中删除的信息。如果您的容器类型 EntityAccumulator
仅包含一个数字,则无法从中生成 Map.Entry<Pair<DateTime, String>, EntityAccumulator>
。
顺便说一句,即使在创建自定义收集器时,您也很少需要使用 class 实现 Collector
接口。您可以简单地使用 Collector.of
,指定功能和特性,创建一个 Collector
.
所以使用你原来的 EntityAccumulator
class(假设 result
应该是 int
而 0.0
是一个错字),你可以使用
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collector.of(EntityAccumulator::new,
EntityAccumulator::calculate,
EntityAccumulator::add,
ea -> ea.result,
Collector.Characteristics.UNORDERED)));
实现同上。也可以分两步执行操作,就像您尝试的那样,使用
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(e -> Pair.of(e.getTimestamp(), e.getName())))
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().collect(
Collector.of(EntityAccumulator::new,
EntityAccumulator::calculate,
EntityAccumulator::add,
ea -> ea.result,
Collector.Characteristics.UNORDERED))));
但是,当然,这只是为了完整性。本回答开头的方案更简单高效。