我想知道如何使用单个流来计算对象的不同字段。我知道我可以轻松地使用流(countedWithStream
)计算对象的单个属性,甚至可以使用for一次计算多个对象(countedWithFor
)。但我实际上很想知道是否可以使用单个流生成相同的输出来实现与countedWithFor
相同的结果。
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.LongStream;
import static java.util.stream.Collectors.*;
class Scratch {
public static void main(String[] args) {
List<AnObject> objects = createObjects();
Map<String, Map<Long, Long>> countedWithStream = countUsingStream(objects);
Map<String, Map<Long, Long>> countedWithFor = countUsingFor(objects);
}
private static Map<String, Map<Long, Long>> countUsingStream(List<AnObject> objects) {
BiFunction<List<AnObject>, Function<AnObject, Long>, Map<Long, Long>> count = (ojs, mpr) -> ojs.stream()
.collect(groupingBy(mpr, counting()));
return ImmutableMap.<String, Map<Long, Long>>builder().put("firstId", count.apply(objects, AnObject::getFirstId))
.put("secondId", count.apply(objects, AnObject::getSecondId))
.build();
}
private static Map<String, Map<Long, Long>> countUsingFor(List<AnObject> objects) {
Map<Long, Long> firstIdMap = new HashMap<>();
Map<Long, Long> secondIdMap = new HashMap<>();
final BiFunction<Long, Map<Long, Long>, Long> count = (k, m) -> k != null ? m.containsKey(k) ? m.put(k, m.get(k) + 1L) : m.put(k, 1L) : null;
for (AnObject object : objects) {
count.apply(object.firstId, firstIdMap);
count.apply(object.secondId, secondIdMap);
}
return ImmutableMap.<String, Map<Long, Long>>builder().put("firstId", firstIdMap)
.put("secondId", secondIdMap)
.build();
}
private static List<AnObject> createObjects() {
return LongStream.range(1, 11)
.mapToObj(Scratch::createObject)
.collect(toList());
}
private static AnObject createObject(long id) {
return new AnObject(id, id);
}
private static class AnObject {
public final long firstId;
public final long secondId;
public AnObject(long firstId,
long secondId) {
this.firstId = firstId;
this.secondId = secondId;
}
public long getFirstId() {
return firstId;
}
public long getSecondId() {
return secondId;
}
}
答案 0 :(得分:1)
您可以使用reduce来完成n次迭代,例如:
Supplier<Map<String, Map<Long, Long>>> mapSupplier = () -> {
Map<String, Map<Long, Long>> outputMap = new HashMap<>();
outputMap.put("firstId", new HashMap<>());
outputMap.put("secondId", new HashMap<>());
return outputMap;
};
Map<String, Map<Long, Long>> reduce = objects.stream().collect(mapSupplier,
(acc, obj) -> {
acc.get("firstId").merge(obj.firstId, 1L, (curv, incr) -> curv + incr);
acc.get("secondId").merge(obj.secondId, 1L, (curv, incr) -> curv + incr);
}
, (acc1, acc2) -> {
acc2.get("firstId").forEach((k, v) -> acc1.get("firstId").merge(k, v, (v1, v2) -> v1 + v2));
acc2.get("secondId").forEach((k, v) -> acc1.get("secondId").merge(k, v, (v1, v2) -> v1 + v2));
});
但这可能不像您想要的那么简洁。
答案 1 :(得分:1)
您可以实现自定义collector,请参见this article中的示例:
public class Scratch {
public static final String FIRST_ID = "firstId";
public static final String SECOND_ID = "secondId";
private static class AnObjectFieldCounter implements Collector<AnObject, Map<String, Map<Long, Long>>, Map<String, Map<Long, Long>>> {
@Override
public Supplier<Map<String, Map<Long, Long>>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<String, Map<Long, Long>>, AnObject> accumulator() {
return (map, obj) -> {
Map<Long, Long> inner;
inner = map.getOrDefault(FIRST_ID, new HashMap<>());
inner.compute(obj.getFirstId(), (id, count) -> (count == null) ? 1 : count + 1);
map.put(FIRST_ID, inner);
inner = map.getOrDefault(SECOND_ID, new HashMap<>());
inner.compute(obj.getSecondId(), (id, count) -> (count == null) ? 1 : count + 1);
map.put(SECOND_ID, inner);
};
}
@Override
public BinaryOperator<Map<String, Map<Long, Long>>> combiner() {
return (a, b) -> {
Map<Long, Long> firstIdCountMap = Stream
.concat(a.get(FIRST_ID).entrySet().stream(), b.get(FIRST_ID).entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, Collectors.summingLong(Map.Entry::getValue)));
Map<Long, Long> secondIdCountMap = Stream
.concat(a.get(SECOND_ID).entrySet().stream(), b.get(SECOND_ID).entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, Collectors.summingLong(Map.Entry::getValue)));
Map<String, Map<Long, Long>> result = new HashMap<>();
result.put(FIRST_ID, firstIdCountMap);
result.put(SECOND_ID, secondIdCountMap);
return result;
};
}
@Override
public Function<Map<String, Map<Long, Long>>, Map<String, Map<Long, Long>>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return new HashSet<>(Arrays.asList(UNORDERED, IDENTITY_FINISH));
}
}
public static void main(String[] args) {
List<AnObject> objects = createObjects();
Map<String, Map<Long, Long>> countedWithCollector = countUsingCollector(objects);
Map<String, Map<Long, Long>> countedWithStream = countUsingStream(objects);
Map<String, Map<Long, Long>> countedWithFor = countUsingFor(objects);
}
private static Map<String, Map<Long, Long>> countUsingCollector(List<AnObject> objects) {
Map<String, Map<Long, Long>> result = objects.stream().collect(new AnObjectFieldCounter());
return ImmutableMap.<String, Map<Long, Long>>builder().putAll(result).build();
}
//...
}