web-dev-qa-db-de.com

Gruppierung nach mehreren Feldnamen in Java 8

Ich habe den Code zum Gruppieren der Objekte nach einem Feldnamen von POJO gefunden. Unten ist der Code dafür:

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);
    }
}

Und die Ausgabe ist (was korrekt ist):

{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}]}

Was aber, wenn ich nach mehreren Feldern gruppieren möchte? Ich kann natürlich ein paar POJOs in der groupingBy()-Methode übergeben, nachdem ich die equals()-Methode in diesem POJO implementiert habe. Gibt es eine andere Option, die ich durch mehrere Felder des angegebenen POJO gruppieren kann?

Z.B. Hier in meinem Fall möchte ich nach Name und Alter gruppieren.

46
Mital Pritmani

Sie haben hier einige Möglichkeiten. Am einfachsten ist es, Ihre Sammler zu verketten:

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

Dann erhalten Sie eine Liste von 18-jährigen Leuten namens Fred, die Sie verwenden würden:

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

Eine zweite Möglichkeit besteht darin, eine Klasse zu definieren, die die Gruppierung darstellt. Dies kann in Person sein:

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);
    }
}

Dann können Sie verwenden:

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

und suchen mit

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

Wenn Sie keine eigene Gruppenklasse implementieren möchten, haben viele Java-Frameworks eine pair-Klasse, die genau für diese Art von Dingen entwickelt wurde. Zum Beispiel: Apache commons pair Wenn Sie eine dieser Bibliotheken verwenden, können Sie den Schlüssel für die Zuordnung zu einem Paar aus Name und Alter machen:

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

und abrufen mit:

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

Ich persönlich mag diese Tuple-Bibliotheken wirklich nicht. Sie scheinen das genaue Gegenteil von gutem OO Design zu sein: Sie verstecken die Absicht, anstatt sie zu entlarven.

Allerdings können Sie die zweiten beiden Optionen kombinieren, indem Sie Ihre eigene Gruppierungsklasse definieren, aber die Implementierung durchführen, indem Sie lediglich Pair erweitern. Dies erspart Ihnen viel Arbeit bei der Definition von equals usw. und verbirgt die Verwendung des Tuples als praktisches Detail der Implementierung wie jede andere Sammlung.

Viel Glück.

96
sprinter

Hier sehen Sie den Code:

Sie können einfach eine Funktion erstellen und die Arbeit für Sie erledigen lassen, sozusagen ein funktionaler Stil!

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

Jetzt können Sie es als Karte verwenden:

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

Prost!

21
Deepesh Rehi

Hallo Sie können Ihre groupingByKey einfach wie zB verketten

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

Die groupingBy-Methode hat den ersten Parameter Function<T,K>. Dabei gilt Folgendes:

@param <T> der Typ der Eingabeelemente

@param <K> die Art der Schlüssel

Wenn wir Lambda durch die anonyme Klasse in Ihrem Code ersetzen, können wir Folgendes sehen:

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

Ändern Sie jetzt den Ausgabeparameter<K>. In diesem Fall habe ich beispielsweise ein Paar aus org.Apache.commons.lang3.Tuple verwendet, um nach Namen und Alter zu gruppieren. Sie können jedoch eine eigene Klasse erstellen, um Gruppen nach Bedarf zu filtern.

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

Nach dem Ersetzen durch Lambda-Back sieht der Code schließlich so aus:

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

Definieren Sie eine Klasse für die Schlüsseldefinition in Ihrer Gruppe.

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

}

Jetzt in deinem Code,

peopleByManyParams = people
            .collect(Collectors.groupingBy(p -> new KeyObj( p.age, p.other1, p.other2 ), Collectors.mapping((Person p) -> p, toList())));
1

Ich musste einen Bericht für ein Cateringunternehmen erstellen, das Mittagessen für verschiedene Kunden anbietet. Mit anderen Worten, Catering kann über ein oder mehrere Unternehmen verfügen, die Bestellungen von Catering annehmen, und es muss wissen, wie viele Mittagessen es jeden Tag für all seine Kunden produzieren muss!

Nur um zu bemerken, habe ich keine Sortierung verwendet, um dieses Beispiel nicht zu komplizieren.

Das ist mein 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