如何使用Collectors.toMap从具有列表的对象集中收集映射

时间:2018-03-21 18:09:02

标签: java-8 java-stream collectors

我有一个带有列表的类Element,我的预期输出是这样的:  Map<String , List<Element>>

{
    1 = [Element3, Element1],
    2 = [Element2, Element1],
    3 = [Element2, Element1], 4=[Element2]
}

我的输入是一组元素对象,我使用forEach来获得所需的结果,但我正在寻找如何使用collectors.toMap来收集它。非常感谢任何投入

Set<Element> changes = new HashSet();

List<String> interesetList = new ArrayList();
interesetList.add("1");
interesetList.add("2");
interesetList.add("3");

Element element = new Element(interesetList);
changes.add(element);

interesetList = new ArrayList();
interesetList.add("2");
interesetList.add("3");
interesetList.add("4");

element = new Element(interesetList);
changes.add(element);

Map<String, List<Element>> collect2 = new HashMap();

changes.forEach(element -> {
    element.getInterestedList().forEach(tracker -> {
        collect2.compute(tracker, ( key , val) -> {
            List<Element> elementList = val == null ? new ArrayList<Element>() : val;
            elementList.add(Element);
            return elementList;
        }); 
    });
});


class Element {

    List<String> interestedList;
    static AtomicInteger sequencer = new AtomicInteger(0);
    String mName;
    public Element(List<String> aList) {
        interestedList = aList;
        mName = "Element" + sequencer.incrementAndGet();
    }
    public List<String> getInterestedList() {
        return interestedList;
    }
    @Override
    public String toString() {
        return mName;
    }
}

1 个答案:

答案 0 :(得分:2)

您可以使用Collectors.groupingBy代替Collectors.toMap以及Collectors.mapping来执行此操作,这会将收集器调整为另一个收集器:

Map<String, List<Element>> result = changes.stream()
    .flatMap(e -> e.getInterestedList().stream().map(t -> Map.entry(t, e)))
    .collect(Collectors.groupingBy(
        Map.Entry::getKey, 
        Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

首先需要使用Stream.flatMap方法,然后将内部列表的元素与当前Element实例配对。我是通过新的Java 9 Map.entry(key, value)方法完成的。如果您尚未使用Java 9,则可以将其更改为new AbstractMap.SimpleEntry<>(key, value)

平面映射后,我们需要收集Map.Entry的实例。因此,我使用Collectors.groupingBy按键对条目进行分类(我们之前已经存储了内部列表的每个元素,也就是您在代码中称为tracker的内容)。然后,由于我们不希望将List<Map.Entry<String, Element>>的实例作为地图的值,我们需要将流的每个Map.Entry<String, Element>转换为Element(&#39} ;为什么我使用Map.Entry::getValue作为Collectors.mapping的第一个参数。我们还需要指定一个下游收集器(此处为Collectors.toList()),以便外部Collectors.groupingBy收集器知道将流的所有适配元素放在哪里,属于每个组。

更短且更有效的方法(类似于您的尝试)可能是:

Map<String, List<Element>> result = new HashMap<>();
changes.forEach(e ->
    e.getInterestedList().forEach(t ->
        result.computeIfAbsent(t, k -> new ArrayList<>()).add(e)));

这使用Map.computeIfAbsent,这非常适合您的用例。