Java8-10-Stream分组与分区详解

上一篇咱们介绍了Strem的概念与实际的一些操做,本篇咱们继续来学习Stream的另外一个重要操做,分组与分区。
咱们在上一篇介绍Stream的操做时,会常常使用到Collectors这个类,这个类其实是一个封装了不少经常使用的汇聚操做的一个工厂类。咱们以前用到过sql

//将结果汇聚到ArrayList中
Collectors.toList();
//将结果汇聚到HashSet中
Collectors.toSet();

以及更为通用的segmentfault

//将结果汇聚到一个指定类型的集合中
Collectors.toCollection(Supplier<C> collectionFactory);

Stream分组app

在实际开发中,对于将一个集合的内容进行分组或分区这种需求也很是常见,因此咱们继续学习下Collectors类中的groupingBy和partitioningBy方法。函数

public static Collector groupingBy(Function<? super T, ? extends K> classifier){
    //...
}

groupingBy接收一个Function类型的变量classifier,classifier被称做分类器,收集器会按着classifier做为key对集合元素进行分组,而后返回Collector收集器对象,假如如今有一个实体Student学习

public class Student {
    private String name;
    private int score;
    private int age;

    public Student(String name,int score,int age){
        this.name = name;
        this.score = score;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

咱们如今按Student的name进行分组,若是使用sql来表示就是select * from student group by name; 再看下使用Stream的方式this

Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getName));

这里咱们使用方法引用(类名::实例方法名)替代lambda表达式(s -> s.getName())的方式来指定classifier分类器,使集合按Student的name来分组。
注意到分组后的返回类型是Map<String, List<Student>>,结果集中会将name做为key,对应的Student集合做为value返回。
那若是按name分组后,想求出每组学生的数量,就须要借助groupingBy另外一个重载的方法code

public static Collector groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream){
    //...
}

第二个参数downstream仍是一个收集器Collector对象,也就是说咱们能够先将classifier做为key进行分组,而后将分组后的结果交给downstream收集器再进行处理对象

//按name分组 得出每组的学生数量 使用重载的groupingBy方法,第二个参数是分组后的操做
Map<String, Long> collect1 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));

Collectors类这里也帮咱们封装好了用于统计数量的counting()方法,这里先了解一下counting()就是将收集器中元素求总数便可,后续咱们会再深刻源码学习。接口

咱们还能够对分组后的数据求平均值源码学习

Map<String, Double> collect2 = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));

averagingDouble方法接收一个ToDoubleFunction参数

@FunctionalInterface
public interface ToDoubleFunction<T> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    double applyAsDouble(T value);
}

ToDoubleFunction实际上也是Function系列函数式接口中的其中一个特例,接收一个参数,返回Double类型(这里是接收一个Student返回score)。由于分组后的集合中每一个元素是Student类型的,因此咱们没法直接对Student进行求平均值

//伪代码
Collectors.averagingDouble(Student))

因此须要将Student转成score再求平均值,Collectors.averagingDouble(Student::getScore))。

Stream分区

针对上面的Student,咱们如今再加一个需求,分别统计一下及格和不及格的学生(分数是否>=60)
这时候符合Stream分区的概念了,Stream分区会将集合中的元素按条件分红两部分结果,key是Boolean类型,value是结果集,知足条件的key是true,咱们看下示例。

Map<Boolean, List<Student>> collect3 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 60));
System.out.println(collect3.get(true));//输出及格的Student
System.out.println(collect3.get(false));//输出不及格的Student

partitioningBy方法接收一个Predicate做为分区判断的依据,知足条件的元素放在key为true的集合中,反之放在key为false的集合中

//partitioningBy方法
public static Collector partitioningBy(Predicate<? super T> predicate) {
    return partitioningBy(predicate, toList());
}

下一篇

相关文章
相关标签/搜索