在java 8中按多个字段名称分组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28342814/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 06:03:35  来源:igfitidea点击:

Group by multiple field names in java 8

javajava-8

提问by Mital Pritmani

I found the code for grouping the objects by some field name from POJO. Below is the code for that:

我从 POJO 中找到了按某些字段名称对对象进行分组的代码。下面是代码:

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

And the output is (which is 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}]}

But what if I want to group by multiple fields? I can obviously pass some POJO in groupingBy()method after implementing equals()method in that POJO but is there any other option like I can group by more than one fields from the given POJO?

但是如果我想按多个字段分组怎么办?在该 POJO 中groupingBy()实现equals()方法后,我显然可以在方法中传递一些 POJO,但是是否还有其他选项,例如我可以按给定 POJO 中的多个字段进行分组?

E.g. here in my case, I want to group by name and age.

例如,就我而言,我想按姓名和年龄分组。

回答by Sarvesh Kumar Singh

Define a class for key definition in your group.

为您的组中的键定义定义一个类。

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

}

Now in your code,

现在在你的代码中,

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

回答by sprinter

You have a few options here. The simplest is to chain your collectors:

你有几个选择。最简单的方法是链接您的收藏家:

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

Then to get a list of 18 year old people called Fred you would use:

然后要获取名为 Fred 的 18 岁人的列表,您可以使用:

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

A second option is to define a class that represents the grouping. This can be inside Person:

第二种选择是定义一个表示分组的类。这可以在 Person 内部:

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

Then you can use:

然后你可以使用:

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

and search with

并搜索

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

Once records have been added to Java (JEP 359) this would be a perfect use case for them:

一旦记录被添加到 Java (JEP 359),这对他们来说将是一个完美的用例:

record NameAge(String name, int age) { }

Will be all that is required to create the required name/age pair with appropriate access, constructor, equals, hashCode methods.

将是使用适当的访问、构造函数、equals、hashCode 方法创建所需名称/年龄对所需的全部内容。

Finally if you don't want to implement your own group class then many of the Java frameworks around have a pairclass designed for this type of thing. For example: apache commons pairIf you use one of these libraries then you can make the key to the map a pair of the name and age:

最后,如果您不想实现自己的组类,那么周围的许多 Java 框架都有一个pair专为此类事物设计的类。例如:apache commons pair如果您使用这些库中的一个,那么您可以将映射的键设为一对名称和年龄:

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

and retrieve with:

并检索:

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

Personally I really dislike these tuple libraries. They seem to be the exact opposite of good OO design: they hide intent instead of exposing it. Once records are added to Java they will be a much better way of expressing the intent as the components will be named rather than anonymous.

我个人非常不喜欢这些元组库。它们似乎与良好的 OO 设计完全相反:它们隐藏意图而不是暴露意图。一旦记录被添加到 Java 中,它们将是一种更好的表达意图的方式,因为组件将被命名而不是匿名。

Having said that you can combine the second two options by defining your own grouping class but implementing it by just extending Pair- that saves you a lot of the work involved in defining equalsetc and hides the use of the tuple as just a convenient implementation detail like any other collection.

话虽如此,您可以通过定义自己的分组类来组合后两个选项,但仅通过扩展来实现它Pair——这为您节省了很多涉及定义equals等的工作,并隐藏了元组的使用,就像任何一个方便的实现细节一样其他集合。



Update

更新

The NameAgeclass is a great use case for Java 14 records:

NameAge班是一个伟大的使用案例的Java 14记录:

record NameAge(String name, int age) { }

This provides all the capabilities required without having to explicitly declare a constructor, equals, hashCode etc. It's a real game-changer for the readability of this type of object that would have typically required a bunch of boilerplate clutter.

这提供了所有所需的功能,而无需显式声明构造函数、equals、hashCode 等。对于通常需要一堆样板杂乱的此类对象的可读性来说,这是一个真正的游戏规则改变者。

回答by Amandeep

Hi You can simply concatenate your groupingByKeysuch as

嗨,您可以简单地连接您的groupingByKey诸如

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

回答by Deepesh Rehi

Here look at the code:

下面看一下代码:

You can simply create a Function and let it do the work for you, kind of functional Style!

你可以简单地创建一个函数,让它为你完成工作,有点函数式风格!

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

Now you can use it as a map:

现在您可以将其用作地图:

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

Cheers!

干杯!

回答by dobrivoje

I needed to make report for a catering firm which serves lunches for various clients. In other words, catering may have on or more firms which take orders from catering, and it must know how many lunches it must produce every single day for all it's clients !

我需要为一家为各种客户提供午餐的餐饮公司做报告。换句话说,餐饮可能有一个或多个接受餐饮订单的公司,它必须知道每天必须为所有客户生产多少午餐!

Just to notice, I didn't use sorting, in order not to over complicate this example.

请注意,我没有使用排序,以免使这个例子过于复杂。

This is my 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));
    }
}

回答by Andrei Smirnov

The groupingBymethod has the first parameter is Function<T,K>where:

groupingBy方法的第一个参数是Function<T,K>

@param <T>the type of the input elements

@param <K>the type of the keys

@param<T>输入元素的类型

@param<K>键的类型

If we replace lambda with the anonymous class in your code, we can see some kind of that:

如果我们用代码中的匿名类替换 lambda,我们可以看到某种形式:

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

Just now change output parameter<K>. In this case, for example, I used a pair class from org.apache.commons.lang3.tuple for grouping by name and age, but you may create your own class for filtering groups as you need.

刚刚更改输出参数<K>。例如,在这种情况下,我使用来自 org.apache.commons.lang3.tuple 的配对类按名称和年龄分组,但您可以根据需要创建自己的类来过滤组。

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

Finally, after replacing with lambda back, code looks like that:

最后,替换回 lambda 后,代码如下所示:

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

回答by Charudatta Joshi

This is how I did grouping by multiple fields branchCode and prdId, Just posting it for someone in need

这就是我按多个字段 branchCode 和 prdId 进行分组的方式,只需将其发布给需要的人

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

        }
    }

Output is as below:

输出如下:

+++++++++++++++++++++++
[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}]

After Formatting it :

格式化后:

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

回答by vinga

You can use List as a classifier for many fields, but you need wrap null values into Optional:

您可以使用 List 作为许多字段的分类器,但您需要将空值包装到 Optional 中:

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