Grouper par plusieurs noms de champs dans java 8


J'ai trouvé le code pour regrouper les objets par un nom de champ de POJO. Voici le code pour cela:

public class Temp {

    static class Person {

        private String name;
        private int age;
        private long salary;

        Person(String name, int age, long salary) {

            this.name = name;
            this.age = age;
            this.salary = salary;
        }

        @Override
        public String toString() {
            return String.format("Person{name='%s', age=%d, salary=%d}", name, age, salary);
        }
    }

    public static void main(String[] args) {
        Stream<Person> people = Stream.of(new Person("Paul", 24, 20000),
                new Person("Mark", 30, 30000),
                new Person("Will", 28, 28000),
                new Person("William", 28, 28000));
        Map<Integer, List<Person>> peopleByAge;
        peopleByAge = people
                .collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p, toList())));
        System.out.println(peopleByAge);
    }
}

Et la sortie est (ce qui est correct):

{24=[Person{name='Paul', age=24, salary=20000}], 28=[Person{name='Will', age=28, salary=28000}, Person{name='William', age=28, salary=28000}], 30=[Person{name='Mark', age=30, salary=30000}]}

Mais que faire si je veux regrouper par plusieurs champs? Je peux évidemment passer un POJO dans la méthode groupingBy() après avoir implémenté la méthode equals() dans ce POJO, mais existe-t-il une autre option comme je peux regrouper par plus d'un champs du POJO donné?

Par exemple ici dans mon cas, je veux regrouper par nom et par âge.

Author: Mital Pritmani, 2015-02-05

6 answers

, Vous avez quelques options ici. Le plus simple est d'enchaîner vos collecteurs:

Map<String, Map<Integer, List<Person>>> map = people
    .collect(Collectors.groupingBy(Person::getName,
        Collectors.groupingBy(Person::getAge));

Ensuite, pour obtenir une liste de personnes de 18 ans appelées Fred, vous utiliseriez:

map.get("Fred").get(18);

Une deuxième option consiste à définir une classe qui représente le regroupement. Cela peut être à l'intérieur de la personne:

class Person {
    public static class NameAge {
        public NameAge(String name, int age) {
            ...
        }

        // must implement equals and hash function
    }

    public NameAge getNameAge() {
        return new NameAge(name, age);
    }
}

, Alors vous pouvez utiliser:

Map<NameAge, List<Person>> map = people.collect(Collectors.groupingBy(Person::getNameAge));

Et recherche avec

map.get(new NameAge("Fred", 18));

Enfin, si vous ne voulez pas implémenter votre propre classe de groupe, la plupart des frameworks Java ont une classe pair conçu pour exactement ce type de chose. Par exemple: apache commons paire Si vous utilisez l'une de ces bibliothèques, vous pouvez alors faire la clé de la carte une paire du nom et de l'âge:

Map<Pair<String, Integer>, List<Person>> map =
    people.collect(Collectors.groupingBy(p -> Pair.of(p.getName(), p.getAge())));

Et récupérer avec:

map.get(Pair.of("Fred", 18));

Personnellement, je n'aime vraiment pas ces bibliothèques de tuples. Ils semblent être l'exact opposé de la bonne conception OO: ils cachent l'intention au lieu de l'exposer.

Cela dit, vous pouvez combiner les deux secondes options en définissant votre propre classe de regroupement mais l'implémenter en étendant simplement Pair - cela vous économise beaucoup de travail impliqué dans la définition de equals etc. et cache l'utilisation du tuple comme un détail d'implémentation pratique comme toute autre collection.

Bonne chance.

 83
Author: sprinter, 2016-02-09 22:57:11

Regardez ici le code:

Vous pouvez simplement créer une fonction et la laisser faire le travail pour vous, sorte de style fonctionnel!

Function<Person, List<Object>> compositeKey = personRecord ->
    Arrays.<Object>asList(personRecord.getName(), personRecord.getAge());

Maintenant, vous pouvez l'utiliser comme une carte:

Map<Object, List<Person>> map =
people.collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

Santé!

 16
Author: Deepesh Rehi, 2017-10-23 07:37:00

Salut, vous pouvez simplement concaténer votre groupingByKey tel que

Map<String, List<Person>> peopleBySomeKey = people
                .collect(Collectors.groupingBy(p -> getGroupingByKey(p), Collectors.mapping((Person p) -> p, toList())));



//write getGroupingByKey() function
private String getGroupingByKey(Person p){
return p.getAge()+"-"+p.getName();
}
 4
Author: Amandeep, 2016-11-02 05:56:29

Définissez une classe pour la définition de clé dans votre groupe.

class KeyObj {

    ArrayList<Object> keys;

    public KeyObj( Object... objs ) {
        keys = new ArrayList<Object>();

        for (int i = 0; i < objs.length; i++) {
            keys.add( objs[i] );
        }
    }

    // Add appropriate isEqual() ... you IDE should generate this

}

Maintenant, dans votre code,

peopleByManyParams = people
            .collect(Collectors.groupingBy(p -> new KeyObj( p.age, p.other1, p.other2 ), Collectors.mapping((Person p) -> p, toList())));
 1
Author: Sarvesh Kumar Singh, 2015-02-05 11:48:41

Le groupingBy méthode a le premier paramètre est Function<T,K> où:

@param <T> le type des éléments d'entrée

@ param {[6] } le type des clés

Si nous remplaçons lambda par la classe anonyme dans votre code, nous pouvons voir une sorte de cela:

people.stream().collect(Collectors.groupingBy(new Function<Person, int>() {
            @Override
            public int apply(Person person) {
                return person.getAge();
            }
        }));

Il suffit de changer le paramètre de sortie <K>. Dans ce cas, par exemple, j'ai utilisé une classe pair de org.Apache.commun.lang3.tuple pour le regroupement par nom et par âge, mais vous pouvez créer votre propre classe pour le filtrage des groupes que vous avez besoin.

people.stream().collect(Collectors.groupingBy(new Function<Person, Pair<Integer, String>>() {
                @Override
                public YourFilter apply(Person person) {
                    return Pair.of(person.getAge(), person.getName());
                }
            }));

Enfin, après avoir remplacé par lambda back, le code ressemble à ceci:

Map<Pair<Integer,String>, List<Person>> peopleByAgeAndName = people.collect(Collectors.groupingBy(p -> Pair.of(person.getAge(), person.getName()), Collectors.mapping((Person p) -> p, toList())));
 1
Author: Andrei Smirnov, 2018-06-20 13:35:19

J'avais besoin de faire rapport pour une entreprise de restauration qui sert des déjeuners pour divers clients. En d'autres termes, la restauration peut avoir sur ou plusieurs entreprises qui prennent des commandes de la restauration, et il doit savoir combien de déjeuners il doit produire chaque jour pour tous ses clients !

Juste pour remarquer, je n'ai pas utilisé le tri, afin de ne pas trop compliquer cet exemple.

Ceci est mon code:

@Test
public void test_2() throws Exception {
    Firm catering = DS.firm().get(1);
    LocalDateTime ldtFrom = LocalDateTime.of(2017, Month.JANUARY, 1, 0, 0);
    LocalDateTime ldtTo = LocalDateTime.of(2017, Month.MAY, 2, 0, 0);
    Date dFrom = Date.from(ldtFrom.atZone(ZoneId.systemDefault()).toInstant());
    Date dTo = Date.from(ldtTo.atZone(ZoneId.systemDefault()).toInstant());

    List<PersonOrders> LON = DS.firm().getAllOrders(catering, dFrom, dTo, false);
    Map<Object, Long> M = LON.stream().collect(
            Collectors.groupingBy(p
                    -> Arrays.asList(p.getDatum(), p.getPerson().getIdfirm(), p.getIdProduct()),
                    Collectors.counting()));

    for (Map.Entry<Object, Long> e : M.entrySet()) {
        Object key = e.getKey();
        Long value = e.getValue();
        System.err.println(String.format("Client firm :%s, total: %d", key, value));
    }
}
 0
Author: dobrivoje, 2017-05-02 07:20:55