使用流计算对象的多个字段

时间:2019-02-01 09:36:34

标签: java java-8 java-stream counting

我想知道如何使用单个流来计算对象的不同字段。我知道我可以轻松地使用流(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;
    }
}

2 个答案:

答案 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();
    }

    //...
}