Java 8 Stream API - 在 Collectors.groupingBy(..) 之后只选择值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35710619/
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-11-03 00:29:30  来源:igfitidea点击:

Java 8 Stream API - Selecting only values after Collectors.groupingBy(..)

javagroupingjava-stream

提问by Ghost93

Say I have the following collection of Studentobjects which consist of Name(String), Age(int) and City(String).

假设我有以下Student对象集合,它们由 Name(String)、Age(int) 和 City(String) 组成。

I am trying to use Java's Stream API to achieve the following sql-like behavior:

我正在尝试使用 Java 的 Stream API 来实现以下类似 sql 的行为:

SELECT MAX(age)
FROM Students
GROUP BY city

Now, I found two different ways to do so:

现在,我找到了两种不同的方法:

final List<Integer> variation1 =
            students.stream()
                    .collect(Collectors.groupingBy(Student::getCity, Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge())))
                    .values()
                    .stream()
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .map(Student::getAge)
                    .collect(Collectors.toList());

And the other one:

而另一个:

final Collection<Integer> variation2 =
            students.stream()
                    .collect(Collectors.groupingBy(Student::getCity,
                            Collectors.collectingAndThen(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()),
                                    optional -> optional.get().getAge())))
                    .values();

In both ways, one has to .values() ...and filter the empty groups returned from the collector.

在这两种方式中,都必须.values() ...过滤收集器返回的空组。

Is there any other way to achieve this required behavior?

有没有其他方法可以实现这种所需的行为?

These methods remind me of over partition bysql statements...

这些方法让我想起了over partition bysql语句...

Thanks

谢谢



Edit:All the answers below were really interesting, but unfortunately this is not what I was looking for, since what I try to get is just the values. I don't need the keys, just the values.

编辑:下面的所有答案都非常有趣,但不幸的是,这不是我想要的,因为我试图得到的只是值。我不需要键,只需要值。

回答by Tagir Valeev

Do not always stick with groupingBy. Sometimes toMapis the thing you need:

不要总是坚持groupingBy。有时toMap是你需要的东西:

Collection<Integer> result = students.stream()
    .collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
    .values();

Here you just create a Mapwhere keys are cities and values are ages. In case when several students have the same city, merge function is used which just selects maximal age here. It's faster and cleaner.

在这里,您只需创建一个Map键是城市,值是年龄的对象。如果几个学生有同一个城市,则使用合并功能,这里只选择最大年龄。它更快更干净。

回答by Holger

As addition to Tagir's great answerusing toMapinstead of groupingBy, here the short solution, if you want to stick to groupingBy:

至于除了Tagir最伟大的答案使用toMap的,而不是groupingBy,这里的短期解决办法,如果你想坚持到groupingBy

Collection<Integer> result = students.stream()
    .collect(Collectors.groupingBy(Student::getCity,
                 Collectors.reducing(-1, Student::getAge, Integer::max)))
    .values();

Note that this three arg reducingcollector already performs a mapping operation, so we don't need to nest it with a mappingcollector, further, providing an identity value avoids dealing with Optional. Since ages are always positive, providing -1is sufficient and since a group will always have at least one element, the identity value will never show up as a result.

注意这三个 argreducing收集器已经执行了映射操作,所以我们不需要用mapping收集器嵌套它,进一步,提供一个标识值避免处理Optional. 由于年龄总是正数,提供-1就足够了,而且由于一个群体总是至少有一个元素,因此身份值永远不会出现。

Still, I think Tagir's toMapbased solution is preferable in this scenario.

不过,我认为toMap在这种情况下,基于Tagir 的解决方案更可取。



The groupingBybased solution becomes more interesting when you want to get the actual students having the maximum age, e.g

groupingBy当您想要获得最大年龄的实际学生时,基于解决方案变得更有趣,例如

Collection<Student> result = students.stream().collect(
   Collectors.groupingBy(Student::getCity, Collectors.reducing(null, BinaryOperator.maxBy(
     Comparator.nullsFirst(Comparator.comparingInt(Student::getAge)))))
).values();

well, actually, even this can also be expressed using the toMapcollector:

好吧,实际上,甚至这也可以使用toMap收集器来表示:

Collection<Student> result = students.stream().collect(
    Collectors.toMap(Student::getCity, Function.identity(),
        BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge)))
).values();

You can express almost everything with both collectors, but groupingByhas the advantage on its side when you want to perform a mutable reductionon the values.

您可以使用两个收集器表达几乎所有内容,但是groupingBy当您想要对值执行可变减少时,它具有优势。

回答by Alexis C.

The second approach calls get()on an Optional; this is usually a bad idea as you don't know if the optional will be empty or not (use orElse(), orElseGet(), orElseThrow()methods instead). While you might argue that in this case there always be a value since you generate the values from the student list itself, this is something to keep in mind.

第二种方法需要get()一个Optional; 这通常是一个坏主意,因为您不知道 optional 是否为空(使用orElse(), orElseGet(),orElseThrow()方法代替)。虽然您可能会争辩说,在这种情况下总是有一个值,因为您从学生列表本身生成值,但这是需要记住的。

Based on that, you might turn the variation 2 into:

基于此,您可以将变体 2 转换为:

final Collection<Integer> variation2 =
     students.stream()
             .collect(collectingAndThen(groupingBy(Student::getCity,
                                                   collectingAndThen(
                                                      mapping(Student::getAge, maxBy(naturalOrder())),
                                                      Optional::get)), 
                                        Map::values));

Although it really starts to be difficult to read, I'll probably use the variant 1:

虽然它确实开始难以阅读,但我可能会使用变体 1:

final List<Integer> variation1 =
        students.stream()
            .collect(groupingBy(Student::getCity,
                                mapping(Student::getAge, maxBy(naturalOrder()))))
            .values()
            .stream()
            .map(Optional::get)
            .collect(toList());

回答by Raghu K Nair

Here is my implementation 

    public class MaxByTest {

        static class Student {

        private int age;
        private int city;        

        public Student(int age, int city) {
            this.age = age;
            this.city = city;
        }

        public int getCity() {
            return city;
        }

        public int getAge() {
            return age;
        }

        @Override
        public String toString() {
            return " City : " + city + " Age : " + age;
        }



    }

    static List<Student> students = Arrays.asList(new Student[]{
        new Student(10, 1),
        new Student(9, 2),        
        new Student(8, 1),        
        new Student(6, 1),
        new Student(4, 1),
        new Student(8, 2),
        new Student(9, 2),
        new Student(7, 2),        
    });

    public static void main(String[] args) {
        final Comparator<Student> comparator = (p1, p2) -> Integer.compare( p1.getAge(), p2.getAge());
        final List<Student> studets =
            students.stream()
                    .collect(Collectors.groupingBy(Student::getCity, 
                            Collectors.maxBy(comparator))).values().stream().map(Optional::get).collect(Collectors.toList());
        System.out.println(studets);
    }
}

回答by Surabhi

        List<BeanClass> list1 = new ArrayList<BeanClass>();
        DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        list1.add(new BeanClass(123,abc,99.0,formatter.parse("2018-02-01")));
        list1.add(new BeanClass(456,xyz,99.0,formatter.parse("2014-01-01")));
        list1.add(new BeanClass(789,pqr,95.0,formatter.parse("2014-01-01")));
        list1.add(new BeanClass(1011,def,99.0,formatter.parse("2014-01-01")));
        Map<Object, Optional<Double>> byDate = list1.stream()
       .collect(Collectors.groupingBy(p -> formatter.format(p.getCurrentDate()),
        Collectors.mapping(BeanClass::getAge, Collectors.maxBy(Double::compare))));