web-dev-qa-db-fra.com

Grouper par plusieurs noms de champs dans Java 8

J'ai trouvé le code permettant de regrouper les objets par un nom de champ provenant de POJO. Ci-dessous 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 le résultat 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 grouper par plusieurs champs? Je peux évidemment passer certains 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 celle que je peux regrouper en plusieurs champs à partir du POJO donné?

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

46
Mital Pritmani

Vous avez quelques options ici. Le plus simple est de chaîner vos collectionneurs:

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ée Fred, vous utiliseriez:

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

Une deuxième option consiste à définir une classe représentant le groupement. 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);
    }
}

Ensuite, vous pouvez utiliser:

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

et rechercher avec

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

Enfin, si vous ne souhaitez pas implémenter votre propre classe de groupe, de nombreux frameworks Java ont une classe pair conçue pour exactement ce type de chose. Par exemple: Apache commons pair Si vous utilisez l'une de ces bibliothèques, vous pouvez alors attribuer à la carte une clé 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 Tuple. Ils semblent être l’opposé exact du bon design OO: ils cachent l’intention au lieu de la révéler.

Cela dit, vous pouvez combiner les deux autres options en définissant votre propre classe de regroupement, mais en l'implémentant en étendant simplement Pair, ce qui vous épargne une grande partie du travail de définition de equals, etc. comme toute autre collection.

Bonne chance.

96
sprinter

Ici, regardez le code:

Vous pouvez simplement créer une fonction et la laisser faire le travail pour vous, genre 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()));

À votre santé!

21
Deepesh Rehi

Bonjour, vous pouvez simplement concaténer votre groupingByKey telle 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
Amandeep

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

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

@param <K> le type des clés

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

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

Il suffit maintenant de modifier le paramètre de sortie<K>. Dans ce cas, par exemple, j’ai utilisé une classe de paires de org.Apache.commons.lang3.Tuple pour regrouper par nom et par âge, mais vous pouvez créer votre propre classe pour filtrer les groupes selon vos besoins.

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

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
Sarvesh Kumar Singh

J'avais besoin de faire un rapport pour une entreprise de restauration qui sert des déjeuners pour divers clients. En d’autres termes, la restauration peut avoir plus d’une entreprise qui prend des commandes, et elle doit savoir combien de repas elle 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
dobrivoje