Gruppieren nach mehreren Feldnamen in Java 8


89

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

Aber was ist, wenn ich nach mehreren Feldern gruppieren möchte? Ich kann natürlich einige POJO in der groupingBy()Methode übergeben, nachdem ich die equals()Methode in diesem POJO implementiert habe , aber gibt es eine andere Option, wie ich nach mehr als einem Feld aus dem angegebenen POJO gruppieren kann?

ZB hier in meinem Fall möchte ich nach Name und Alter gruppieren.


1
Ein Trick besteht darin, aus allen Feldern eine eindeutige Zeichenfolge zu generieren.
Marko Topolnik

3
Übrigens mappingals nachgeschalteter Kollektor ist in dem von Ihnen geposteten Code redundant.
Marko Topolnik

8
Schnelle und schmutzige Lösung ist people.collect(groupingBy(p -> Arrays.asList(p.name, p.age))).
Mischa

Antworten:


161

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

Um eine Liste von 18-jährigen Leuten namens Fred zu erhalten, würden Sie Folgendes verwenden:

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

Eine zweite Option besteht darin, eine Klasse zu definieren, die die Gruppierung darstellt. Dies kann innerhalb der Person sein. Dieser Code verwendet a record, kann aber genauso gut eine Klasse (mit equalsund hashCodedefiniert) in Java-Versionen sein, bevor JEP 359 hinzugefügt wurde:

class Person {
    record NameAge(String name, int age) { }

    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 keinen eigenen Gruppendatensatz implementieren möchten, haben viele der Java-Frameworks eine pairKlasse, die für diese Art von Dingen entwickelt wurde. Beispiel: Apache Commons-Paar Wenn Sie eine dieser Bibliotheken verwenden, können Sie den Schlüssel für die Karte 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));

Persönlich sehe ich nicht viel Wert in generischen Tupeln, da Datensätze in der Sprache verfügbar sind, da Datensätze die Absicht besser anzeigen und sehr wenig Code erfordern.


5
Function<T,U>verbirgt auch Absichten in diesem Sinne --- aber Sie werden niemanden sehen, der für jeden Mapping-Schritt seine eigene Funktionsschnittstelle deklariert; Die Absicht ist bereits im Lambda-Körper vorhanden. Gleiches gilt für Tupel: Sie eignen sich hervorragend als Klebetypen zwischen API-Komponenten. BTW Scala Fallklassen sind IMHO ein großer Gewinn sowohl in Bezug auf Prägnanz und Absicht Exposition.
Marko Topolnik

1
Ja, ich verstehe deinen Standpunkt. Ich denke (wie immer), es hängt davon ab, wie sie verwendet werden. Das Beispiel, das ich oben gegeben habe - ein Paar als Schlüssel für eine Karte verwenden - ist ein gutes Beispiel dafür, wie man es nicht macht. Ich bin mit Scala nicht allzu vertraut - ich muss anfangen, es zu lernen, wenn ich gute Dinge höre.
Sprinter

1
Man stelle sich vor in der Lage zu erklären , NameAgeals Einzeiler: case class NameAge { val name: String; val age: Int }--- und Sie erhalten equals, hashCodeund toString!
Marko Topolnik

1
Schön - ein weiterer Artikel, der in meine Warteschlange gestellt wurde. Es ist leider FIFO!
Sprinter

@sprinter Der Typ im ersten Code-Snippet ist nicht korrekt und sollte inMap<String, Map<Integer, List<Person>>> map
Kasur

38

Hier sehen Sie den Code:

Sie können einfach eine Funktion erstellen und sie die Arbeit für Sie erledigen lassen, eine Art 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!


2
Ich habe diese Lösung aber anders verwendet. Funktion <Person, String> composKey = personRecord -> StringUtils.join (personRecord.getName (), personRecord.getAge ());
Bpedroso

8

Die groupingByMethode hat den ersten Parameter Function<T,K>:

@param <T>der Typ der Eingabeelemente

@param <K>der Typ 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 gerade den Ausgabeparameter <K>. In diesem Fall habe ich beispielsweise eine Paarklasse aus org.apache.commons.lang3.tuple zum Gruppieren nach Name und Alter verwendet. Sie können jedoch auch eine eigene Klasse zum Filtern von Gruppen nach Bedarf erstellen.

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

Was ist mit List<String>?
Alex78191

6

Hallo, Sie können einfach Ihre groupingByKeywie 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();
}

2

Sie können List als Klassifizierer für viele Felder verwenden, müssen jedoch Nullwerte in Optional umschließen:

Function<String, List> classifier = (item) -> List.of(
    item.getFieldA(),
    item.getFieldB(),
    Optional.ofNullable(item.getFieldC())
);

Map<List, List<Item>> grouped = items.stream()
    .collect(Collectors.groupingBy(classifier));

1

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 Ihrem Code,

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

3
Das ist nur eine Neuerfindung Ararys.asList()- was übrigens eine gute Wahl für den Fall von OP ist.
Marko Topolnik

Und auch ähnlich dem Pairim anderen Beispiel erwähnten Beispiel, jedoch ohne Argumentationsbeschränkung.
Benny Bottema

Auch müssen Sie dies unveränderlich machen. (und berechnen Sie die hashCode) einmal)
RobAu

1

Ich musste einen Bericht für eine Catering-Firma erstellen, die Mittagessen für verschiedene Kunden serviert. Mit anderen Worten, das Catering hat möglicherweise mehrere Firmen, die Bestellungen vom Catering entgegennehmen, und es muss wissen, wie viele Mittagessen es jeden Tag für alle 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

So habe ich nach mehreren Feldern branchCode und prdId gruppiert. Ich habe es nur für jemanden in Not gepostet

    import java.math.BigDecimal;
    import java.math.BigInteger;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    /**
     *
     * @author charudatta.joshi
     */
    public class Product1 {

        public BigInteger branchCode;
        public BigInteger prdId;
        public String accountCode;
        public BigDecimal actualBalance;
        public BigDecimal sumActBal;
        public BigInteger countOfAccts;

        public Product1() {
        }

        public Product1(BigInteger branchCode, BigInteger prdId, String accountCode, BigDecimal actualBalance) {
            this.branchCode = branchCode;
            this.prdId = prdId;
            this.accountCode = accountCode;
            this.actualBalance = actualBalance;
        }

        public BigInteger getCountOfAccts() {
            return countOfAccts;
        }

        public void setCountOfAccts(BigInteger countOfAccts) {
            this.countOfAccts = countOfAccts;
        }

        public BigDecimal getSumActBal() {
            return sumActBal;
        }

        public void setSumActBal(BigDecimal sumActBal) {
            this.sumActBal = sumActBal;
        }

        public BigInteger getBranchCode() {
            return branchCode;
        }

        public void setBranchCode(BigInteger branchCode) {
            this.branchCode = branchCode;
        }

        public BigInteger getPrdId() {
            return prdId;
        }

        public void setPrdId(BigInteger prdId) {
            this.prdId = prdId;
        }

        public String getAccountCode() {
            return accountCode;
        }

        public void setAccountCode(String accountCode) {
            this.accountCode = accountCode;
        }

        public BigDecimal getActualBalance() {
            return actualBalance;
        }

        public void setActualBalance(BigDecimal actualBalance) {
            this.actualBalance = actualBalance;
        }

        @Override
        public String toString() {
            return "Product{" + "branchCode:" + branchCode + ", prdId:" + prdId + ", accountCode:" + accountCode + ", actualBalance:" + actualBalance + ", sumActBal:" + sumActBal + ", countOfAccts:" + countOfAccts + '}';
        }

        public static void main(String[] args) {
            List<Product1> al = new ArrayList<Product1>();
            System.out.println(al);
            al.add(new Product1(new BigInteger("01"), new BigInteger("11"), "001", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("11"), "002", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "003", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "004", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("12"), "005", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("01"), new BigInteger("13"), "006", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("11"), "007", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("11"), "008", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "009", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "010", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("12"), "011", new BigDecimal("10")));
            al.add(new Product1(new BigInteger("02"), new BigInteger("13"), "012", new BigDecimal("10")));
            //Map<BigInteger, Long> counting = al.stream().collect(Collectors.groupingBy(Product1::getBranchCode, Collectors.counting()));
            // System.out.println(counting);

            //group by branch code
            Map<BigInteger, List<Product1>> groupByBrCd = al.stream().collect(Collectors.groupingBy(Product1::getBranchCode, Collectors.toList()));
            System.out.println("\n\n\n" + groupByBrCd);

             Map<BigInteger, List<Product1>> groupByPrId = null;
              // Create a final List to show for output containing one element of each group
            List<Product> finalOutputList = new LinkedList<Product>();
            Product newPrd = null;
            // Iterate over resultant  Map Of List
            Iterator<BigInteger> brItr = groupByBrCd.keySet().iterator();
            Iterator<BigInteger> prdidItr = null;    



            BigInteger brCode = null;
            BigInteger prdId = null;

            Map<BigInteger, List<Product>> tempMap = null;
            List<Product1> accListPerBr = null;
            List<Product1> accListPerBrPerPrd = null;

            Product1 tempPrd = null;
            Double sum = null;
            while (brItr.hasNext()) {
                brCode = brItr.next();
                //get  list per branch
                accListPerBr = groupByBrCd.get(brCode);

                // group by br wise product wise
                groupByPrId=accListPerBr.stream().collect(Collectors.groupingBy(Product1::getPrdId, Collectors.toList()));

                System.out.println("====================");
                System.out.println(groupByPrId);

                prdidItr = groupByPrId.keySet().iterator();
                while(prdidItr.hasNext()){
                    prdId=prdidItr.next();
                    // get list per brcode+product code
                    accListPerBrPerPrd=groupByPrId.get(prdId);
                    newPrd = new Product();
                     // Extract zeroth element to put in Output List to represent this group
                    tempPrd = accListPerBrPerPrd.get(0);
                    newPrd.setBranchCode(tempPrd.getBranchCode());
                    newPrd.setPrdId(tempPrd.getPrdId());

                    //Set accCOunt by using size of list of our group
                    newPrd.setCountOfAccts(BigInteger.valueOf(accListPerBrPerPrd.size()));
                    //Sum actual balance of our  of list of our group 
                    sum = accListPerBrPerPrd.stream().filter(o -> o.getActualBalance() != null).mapToDouble(o -> o.getActualBalance().doubleValue()).sum();
                    newPrd.setSumActBal(BigDecimal.valueOf(sum));
                    // Add product element in final output list

                    finalOutputList.add(newPrd);

                }

            }

            System.out.println("+++++++++++++++++++++++");
            System.out.println(finalOutputList);

        }
    }

Die Ausgabe ist wie folgt:

+++++++++++++++++++++++
[Product{branchCode:1, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, Product{branchCode:1, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, Product{branchCode:1, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}, Product{branchCode:2, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, Product{branchCode:2, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, Product{branchCode:2, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}]

Nach dem Formatieren:

[
Product{branchCode:1, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, 
Product{branchCode:1, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, 
Product{branchCode:1, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}, 
Product{branchCode:2, prdId:11, accountCode:null, actualBalance:null, sumActBal:20.0, countOfAccts:2}, 
Product{branchCode:2, prdId:12, accountCode:null, actualBalance:null, sumActBal:30.0, countOfAccts:3}, 
Product{branchCode:2, prdId:13, accountCode:null, actualBalance:null, sumActBal:10.0, countOfAccts:1}
]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.