it-swarm.com.ru

Создать вложенный родительский дочерний список в Java 8

Я новичок в 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?

9
user3378550

Другой подход, не требующий переопределения equals и hashCode:

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() ) 
        ) 
    )
;
2
tsolakp

Я бы посоветовал вам реализовать equals и hashcode в вашем классе Person

Пример:

@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 - Как и сейчас, я предполагаю, что два или более человека считаются равными на основе name и age, однако, если он основан только на name или только на age, вам необходимо настроить методы equals/hashcode совсем немного.

8
Aomine

Предполагая, что Person реализует hashCode и equals последовательно, вы можете собрать в 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 работает). Это не всегда так, особенно если вы не хотите ломать encapsulation, вы не должны позволять изменять адреса ваших объектов 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));
5
Federico Peralta Schaffner

Этот ответ будет очень похож на недавно удаленный ответ:

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 и hashCode соответственно для Person.

1
Jacob G.