【问题标题】:Java 8 mapping to sub list entries of a collection using streams and collectorsJava 8 使用流和收集器映射到集合的子列表条目
【发布时间】:2016-03-28 20:54:49
【问题描述】:

我有一个Person 对象的集合:。

public class Person {

  String name;

  ChildrenListHolder childrenListHolder;
}

public class ChildrenListHolder {
   List<Children> children;
}

public class Children {
   String childrensName;
}

(实体结构由第三方给出。)

现在,我需要一个 Map&lt;String,List&lt;Person&gt;&gt;childrensName -> 人员列表

例如(简体):

Person father: {name: "John", childrensListHolder -> {"Lisa", "Jimmy"}}
Person mother: {name: "Clara", childrensListHolder -> {"Lisa", "Paul"}}
Person george: {name: "George", childrensListHold -> "Paul"}}

我需要的地图是

Map<String, List<Person>> map: {"Lisa"  -> {father, mother},
                                "Jimmy" -> {father},
                                "Paul"  -> {mother, george}}

我可以用一堆 for 和 if 来做到这一点。但是我怎样才能使用流和收集器来做到这一点。我尝试了很多方法,但我无法得到预期的结果。 TIA。

【问题讨论】:

    标签: java java-8 java-stream collectors


    【解决方案1】:

    给定List&lt;Person&gt; persons,您可以拥有以下内容

    Map<String,List<Person>> map =
        persons.stream()
               .flatMap(p -> p.childrenListHolder.children.stream().map(c -> new AbstractMap.SimpleEntry<>(c, p)))
               .collect(Collectors.groupingBy(
                 e -> e.getKey().childrensName,
                 Collectors.mapping(Map.Entry::getValue, Collectors.toList())
               ));
    

    这是在人员上创建一个流。然后每个人都被一个包含孩子的元组和每个孩子的人平面映射。最后,我们按孩子姓名分组,并将所有人员收集到一个列表中。

    假设有适当的构造函数的示例代码:

    public static void main(String[] args) {
        List<Person> persons = Arrays.asList(
            new Person("John", new ChildrenListHolder(Arrays.asList(new Children("Lisa"), new Children("Jimmy")))),
            new Person("Clara", new ChildrenListHolder(Arrays.asList(new Children("Lisa"), new Children("Paul")))),
            new Person("George", new ChildrenListHolder(Arrays.asList(new Children("Paul"))))
        );
    
        Map<String,List<Person>> map =
            persons.stream()
                   .flatMap(p -> p.childrenListHolder.children.stream().map(c -> new AbstractMap.SimpleEntry<>(c, p)))
                   .collect(Collectors.groupingBy(
                     e -> e.getKey().childrensName,
                     Collectors.mapping(Map.Entry::getValue, Collectors.toList())
                   ));
    
        System.out.println(map);
    }
    

    【讨论】:

    • 谢谢。不幸的是,它不起作用。 flatMap 调用返回Stream&lt;Object&gt;,因此使用e.getKey 收集不起作用。
    • @t777 嗯,我刚刚在 Eclipse Mars.2 中运行了该代码,它运行良好。您使用的是哪个 IDE?
    • 我正在使用 Eclipse Luna 2 (4.4.2)
    • @t777 这可能是 Luna 的限制。你可以试试stream().&lt;Map.Entry&lt;Children,Person&gt;&gt; flatMap(...)吗?
    • 我刚刚在 Eclipse Mars.2 上尝试过,它对我来说也很好用。 ;) 很奇怪。
    【解决方案2】:

    我可以用一堆 for 和 if 来做到这一点。

    我知道您要求提供流/收集器解决方案,但无论如何使用 Map#computeIfAbsent 的嵌套 for 循环也可以正常工作:

    Map<String, List<Person>> map = new HashMap<>();
    for(Person p : persons) {
        for(Children c : p.childrenListHolder.children) {
            map.computeIfAbsent(c.childrensName, k -> new ArrayList<>()).add(p);
        }
    }
    

    这是使用集合中引入的新 forEach 方法编写的:

    Map<String, List<Person>> map = new HashMap<>();
    persons.forEach(p -> p.childrenListHolder.children.forEach(c -> map.computeIfAbsent(c.childrensName, k -> new ArrayList<>()).add(p)));
    

    当然,它不像 Tunaki 的解决方案 (+1) 那样是单行的,也不是容易并行化的,但是您也不需要“一堆” if 来实现这一点(并且您还避免创建临时地图条目实例)。

    【讨论】:

    • 是的,这也是一个不错的解决方案。好处是你不需要一个元组来保存孩子和人。
    • 谢谢。我也喜欢你的解决方案。我希望,我可以接受两个答案。 ;)
    猜你喜欢
    • 2017-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-22
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多