使用流和收集器将Java 8映射到集合的子列表条目

7

我有一个Person对象的集合:

public class Person {

  String name;

  ChildrenListHolder childrenListHolder;
}

public class ChildrenListHolder {
   List<Children> children;
}

public class Children {
   String childrensName;
}

该实体结构由第三方提供。

现在,我需要一个 Map<String,List<Person>> childrensName -> person-list

例如(简化版):

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语句来实现。但是如何使用流和收集器来实现呢?我尝试了许多方法,但是无法得到预期的结果。谢谢。

2个回答

9

给定一个 List<Person> 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<Object>,因此使用e.getKey进行收集不起作用。 - t777
1
@t777 嗯,我刚在Eclipse Mars.2中运行了那段代码,它正常工作。你用的是哪个IDE? - Tunaki
我正在使用Eclipse Luna 2(4.4.2) - t777
1
@t777 这可能是Luna的限制。你可以尝试使用 stream().<Map.Entry<Children,Person>> flatMap(...) 吗? - Tunaki
我刚刚在 Eclipse Mars.2 上尝试了一下,对我来说也很好用。;) 非常奇怪。 - t777
有了这种类型的转换,Luna也可以正常工作。 :) 谢谢! - t777

5

我可以用一堆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的解决方案一样简单的一行代码,也不容易并行化。但你也不需要使用大量的if语句来实现它(而且还可以避免创建临时的映射实例)。

2
是的,那也是一个不错的解决方案。优点是您不需要一个元组来保存孩子和人的信息。 - Tunaki
谢谢。我也喜欢你的解决方案。但愿我能接受两个答案。;) - t777

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接