在Java 8中创建嵌套的父子列表

时间:2018-02-04 00:11:19

标签: java list java-8 iteration java-stream

我是Java 8的新手,需要解决以下问题。

我有两个课程如下:

class Person {
    String name;
    int age;
    List<Address> address;
}

class Address {
    String street;
    String city;
    String country;
}

现在我有一个来自数据库的列表:

List<Person> findPerson;

adam
26
<123, xyz, yyy>

adam
26
<456, rrr, kkk>

bill
31
<666, uuu, hhh>

现在我需要将同一个人对象与不同的地址对象合并为一个,如下所示?

List<Person> findPerson;

adam
26
<123, xyz, 456>
<456, rrr, 123>

bill
31
<666, uuu, 999>

如何在Java 8流中完成此操作?

4 个答案:

答案 0 :(得分:8)

我建议您在equals课程中实施hashcodePerson

示例:

@Override
public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;

       Person person = (Person) o;

       if (age != person.age) return false;
       return name != null ? name.equals(person.name) : person.name == null;
}

@Override
public int hashCode() {
       int result = name != null ? name.hashCode() : 0;
       result = 31 * result + age;
       return result;
}

然后您可以使用Map<Person, List<Address>>作为结果集的接收器类型。

Map<Person, List<Address>> resultSet = findPerson.stream()
    .collect(
        Collectors.groupingBy(
            Function.identity(),
            Collectors.flatMapping(
                p -> p.getAddress().stream(), 
                Collectors.toList()
            )
        )
    )
;

此解决方案使用flatMapping收集器,该收集器仅在Java-9时可用。

如果你还在使用Java-8,那么你可以这样做:

Map<Person, List<Address>> resultSet = findPerson.stream()
    .collect(
        Collectors.groupingBy(
            Function.identity(),
            Collectors.collectingAndThen(
                Collectors.mapping(
                    Person::getAddress, 
                    Collectors.toList()
                ),
                lists -> lists.stream()
                    .flatMap(List::stream)
                    .collect( Collectors.toList() )
            )
        )
    )   
;

note - 按原样,我目前假设基于nameage的两个或更多人被认为是平等的,但是,如果它仅基于{{1}或者只是name,那么您需要稍微调整一下age / equals方法。

答案 1 :(得分:5)

假设Person一致地实施hashCodeequals,您可以收集到Map

Map<Person, Person> map = findPerson.stream()
    .collect(Collectors.toMap(
        p -> p,
        p -> p,
        (oldPerson, newPerson) -> { 
            oldPerson.getAddress().addAll(newPerson.getAddress()); 
            return oldPerson; 
        }));

这使用Collectors.toMap,其工作原理是合并新人与地图中已有的老人,合并我们的意思是添加地址。

现在,在地图的值中,您有合并的人:

Collection<Person> result = map.values();

如果您需要返回一个列表:

List<Person> result = new ArrayList<>(map.values());

修改

这假设每个人的List<Address> 可变(以便addAll有效)。情况并非总是如此,特别是如果您不想破坏封装,您不应该从外部修改Person个对象的地址。

在这种情况下,您可以在Person中提供一个方法来负责合并地址而不破坏封装:

public Person merge(Person another) {
    this.address.addAll(another.address);
    return this;
}

或者如果地址列表是不可变的:

public Person merge(Person another) {
    List<Address> merged = new ArrayList<>(this.address);
    merged.addAll(another.address);
    this.address = merged;
    return this;
}

现在收集器中的代码可以重构为:

Map<Person, Person> map = findPerson.stream()
    .collect(Collectors.toMap(
        p -> p, 
        p -> p,
        Person::merge));

答案 2 :(得分:2)

另一种方法,不需要覆盖equalshashCode

List<Person> merged = findPerson.stream()
    .collect( 
        Collectors.collectingAndThen(
            Collectors.toMap( 
                (p) -> new AbstractMap.SimpleEntry<>( p.getName(), p.getAge() ), 
                Function.identity(), 
                (left, right) -> { 
                    left.getAddress().addAll( right.getAddress() ); 
                    return left; 
                }
            ),        
            ps -> new ArrayList<>( ps.values() ) 
        ) 
    )
;

答案 3 :(得分:1)

这个答案与最近删除的答案非常相似:

findPerson = findPerson.stream()
                       .collect(Collectors.groupingBy(Person::getName,
                                Collectors.reducing((p1, p2) -> {
                                    p1.getAddress().addAll(p2.address);
                                    return p1;
                       }))).values().stream()
                           .filter(Optional::isPresent)
                           .map(Optional::get)
                           .collect(Collectors.toList());

使用Java 9,可以用Optional::stream的单个调用替换底部的两行。

使用您的示例,结果如下:

[Person[bill, 31, [Address[666, uuu, hhh]]], Person[adam, 26, [Address[123, xyz, yyy], Address[456, rrr, kkk]]]]

注意:请务必为equals适当覆盖hashCodePerson