Сборщики Java 8 группируются по и в отдельный класс

У меня есть список Persons с повторяющимися именами.

class Person {
  String name;
}

Я хочу преобразовать его в список GroupedPersons, которые содержат общее имя и список всех лиц, имеющих это имя.

class GroupedPerson {
  String name;
  List<A> as;
}

Можно ли сделать это с одним коллектором и без промежуточного отображения или дополнительных классов?


person danial    schedule 08.02.2018    source источник
comment
есть ли у GroupedPerson как сеттеры, так и геттеры? Или он неизменен?   -  person Eugene    schedule 08.02.2018
comment
Да, у него есть все конструкторы/геттеры/сеттеры по мере необходимости.   -  person danial    schedule 08.02.2018


Ответы (3)


Я полагаю, что один из способов:

 persons
   .stream()          
   .collect(Collectors.collectingAndThen(Collectors.groupingBy(
           Person::getName),
           map -> {
               return map.entrySet()
                         .stream()
                         .map(en -> new GroupedPerson(en.getKey(), en.getValue()))
                         .collect(Collectors.toList());
}));

Или вы можете использовать toMap:

persons  
   .stream()
   .collect(Collectors.toMap(
            Person::getName,
            x -> {
                  List<Person> l = new ArrayList<>();
                  l.add(x);
                  return new GroupedPerson(x.getName(), l);
            },
            (left, right) -> {
                 left.getAs().addAll(right.getAs());
                 return left;
            }))
   .values();
person Eugene    schedule 08.02.2018
comment
Это выглядит хорошо, но для этого нужно построить карту, а затем нам нужно пропарить эту карту. Есть ли другой сборщик, который мог бы напрямую собирать его в объект без предварительного создания карты? - person danial; 08.02.2018
comment
@danial как бы это себе представить? вам все еще нужен Map, чтобы сгруппировать это - person Eugene; 08.02.2018
comment
Да, это имеет смысл, я думаю, что это было бы оптимальным решением тогда. - person danial; 08.02.2018
comment
@Danial, на самом деле, вы можете использовать AbstractList, чтобы сделать упрощенное представление карты, если вы используете карту, которая гарантирует порядок итерации. - person Joshua Taylor; 09.02.2018

Да, вы можете это сделать. Вот как это сделать:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class GroupedStream {

    public static void main(String[] args) {
        List<GroupedPerson> groupedPersons = Arrays.asList("john harry john harry sam jordon bill steve bill".split(" "))
            .stream()
            .map(name -> new Person(name))
            .map(person -> new Object[] {person.name, new ArrayList<>()})
            .collect(Collectors.toMap(tuple-> (String) tuple[0], tuple -> (List<Person>) tuple[1] ))
            .entrySet()
            .stream()
            .map(entry -> new GroupedPerson(entry.getKey(), entry.getValue()))
            .collect(Collectors.toList());
    }

    static class Person {
        public final String name;

        public Person(String name) {
            super();
            this.name = name;
        }

    }

    static class GroupedPerson {

        public final String name;
        public final List<Person> person;

        public GroupedPerson(String name, List<Person> person) {
            this.name = name;
            this.person = person;
        }

    }

}
person suenda    schedule 08.02.2018

если вы измените свою модель GroupedPerson, чтобы она выглядела примерно так:

public class GroupedPerson {
    private String name;
    private List<Person> people;

    public GroupedPerson(String name) {
        this.name = name;
        people = new ArrayList<>();
    }

    public void addPeople(List<Person> people) {
        this.people.addAll(people);
    }

    public void addPerson(Person person){
           people.add(person);
    }

    public List<Person> getPeople(){
        return Collections.unmodifiableList(people);
    }

    public String getName() {
        return name;
    }    
}

Затем у вас может быть Collection из GroupedPerson объектов с именами и всеми соответствующими людьми с этим конкретным именем, например:

Collection<GroupedPerson> resultSet = peopleList
                .stream()
                .collect(Collectors.toMap(Person::getName,
                        p -> {
                            GroupedPerson groupedPerson = new GroupedPerson(p.getName());
                            groupedPerson.addPerson(p);
                            return groupedPerson;
                        },
                        (p, p1) -> {
                            p.addPeople(p1.getPeople());
                            return p;
                        }
                )).values();

если по какой-то причине вы не хотите, чтобы тип получателя был Collection<T>, вы можете преобразовать его в определенный тип коллекции, если считаете это целесообразным, просто выполнив действие.

List<GroupedPerson> result = new ArrayList<>(resultSet);
person Ousmane D.    schedule 08.02.2018