欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java8 -- 05 -- Collectors类常用方法解析

程序员文章站 2024-03-12 23:30:09
...

在使用流之前,我们先来了解下 Collectors 类,因为在使用流的过程中,会经常用到该类的相关方法

Collectors 工具类提供了许多静态工具方法来创建收集器,比如将元素装进一个集合中、将元素分组、根据不同标准对元素进行汇总等,现在我们就来看看它具体使用


  • Student.java

    public class Student {
    
        private String name;
        private int age;
        private double score;
    
        public Student(String name, int age, int score){
            this.name = name;
            this.age = age;
            this.score = score;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public double getScore() {
            return score;
        }
    
        public void setScore(double score) {
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", score=" + score +
                    '}';
        }
    }
    

一、toList()

  • 将流中所有元素收集到一个 List 中

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
            
    List<String> studentNameList = studentList.stream().map(Student::getName).collect(toList());
    // [小白, 小黑, 小红, 小明]
    System.out.println(studentNameList);
    

二、toMap()

  • 将流中所有元素收集到一个 Map 中

  • 该方法有四个参数

    • Function<? super T, ? extends K> keyMapper

      • 映射函数,用于生成 key
    • Function<? super T, ? extends U> valueMapper

      • 映射函数,用于生成 value
    • BinaryOperator<U> mergeFunction

      • 合并函数,用于解决 key 重复的情况
    • Supplier<M> mapSupplier

      • 供给函数,返回一个空的 Map,用于存放 key、value,默认实现为 HashMap::new
  • 该方法有三个重载方法,为了方便展示我们将参数类型省去

    • toMap(keyMapper, valueMapper)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<String, Integer> studentMap = studentList.stream()
          .collect(toMap(Student::getName, Student::getAge));
      // {小明=22, 小白=20, 小红=22, 小黑=21}
      System.out.println(studentMap);
      
      • 此处以学生姓名作为 key,学生年龄作为 value,存放到 Map 中
    • toMap(keyMapper, valueMapper, mergeFunction)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Integer, String> studentMap = studentList.stream()
          .collect(toMap(Student::getAge, Student::getName, (oldValue, newValue) -> {
              // 【小红】被替换成了【小明】
              System.out.println("【" + oldValue + "】被替换成了【" + newValue + "】");
              return newValue;
          }));
      // {20=小白, 21=小黑, 22=小明}
      System.out.println(studentMap);
      
      • 此处以学生年龄作为 key,会存在 key 重复的情况,如果不指定合并函数的话,程序会报异常

      • 此处当 key 重复时,取新 value 覆盖旧 value,从输出中我们可以很清晰地看到 value 的替换过程

    • toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Integer, String> studentMap = studentList.stream()
          .collect(toMap(Student::getAge, Student::getName,
          (oldValue, newValue) -> newValue, LinkedHashMap::new));
      // {20=小白, 21=小黑, 22=小明}
      System.out.println(studentMap);
      
      • 此处我们将默认的 HashMap::new 替换成 LinkedHashMap::new,使用 LinkedHashMap 来存储数据

三、toConcurrentMap()

  • toConcurrentMap() 用法与 toMap() 用法相同

  • 不同之处在于 toConcurrentMap() 返回的是 ConcurrentMap,toMap() 返回的是 Map


四、groupingBy()

  • 根据流中元素的某个属性值对流中元素进行分组,并将该属性值作为结果 Map 的 key

  • 该方法有三个参数

    • Function<? super T, ? extends K> classifier

      • 分类函数,用于将输入元素映射到 key
    • Supplier<M> mapFactory

      • 供给函数,返回一个空的 Map,用于存放 key、value,默认实现为 HashMap::new
    • Collector<? super T, A, D> downstream

      • 归约收集器
  • 该方法有三个重载方法,为了方便展示我们将参数类型省去

    • groupingBy(classifier)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Integer, List<Student>> studentMap = studentList
          .stream().collect(groupingBy(Student::getAge));
      // {20=[Student(name=小白, age=20, score=90.0)], 
      // 21=[Student(name=小黑, age=21, score=95.0)],
      // 22=[Student(name=小红, age=22, score=80.0), Student(name=小明, age=22, score=82.0)]}
      System.out.println(studentMap);
      
      • 此处以学生年龄作为分组条件对集合进行分组
    • groupingBy(classifier, downstream)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Integer, Long> studentMap = studentList
          .stream().collect(groupingBy(Student::getAge, counting()));
      // {20=1, 21=1, 22=2}
      System.out.println(studentMap);
      
      • 此处以学生年龄作为分组条件对集合进行分组,然后再对分组结果进行归约统计
    • groupingBy(classifier, mapFactory, downstream)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Integer, Long> studentMap = studentList
          .stream().collect(groupingBy(Student::getAge, LinkedHashMap::new, counting()));
      // {20=1, 21=1, 22=2}
      System.out.println(studentMap);
      
      • 此处我们将默认的 HashMap::new 替换成 LinkedHashMap::new,使用 LinkedHashMap 来存储数据
  • 此外,我们可以将多个 groupingBy 嵌套使用,从而实现多级分组

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Map<Integer, Map<Double, List<Student>>> studentMap = studentList
        .stream().collect(groupingBy(Student::getAge, groupingBy(Student::getScore)));
    // {20={90.0=[Student(name=小白, age=20, score=90.0)]}, 
    // 21={95.0=[Student(name=小黑, age=21, score=95.0)]}, 
    // 22={82.0=[Student(name=小明, age=22, score=82.0)], 80.0=[Student(name=小红, age=22, score=80.0)]}}
    System.out.println(studentMap);
    
    • 此处以学生年龄作为分组条件对集合进行分组,再以学生分数作为分组条件对集合进行二次分组

五、groupingByConcurrent()

  • groupingByConcurrent() 用法与 groupingBy() 用法相同

  • 不同之处在于 groupingByConcurrent() 返回的是 ConcurrentMap,groupingBy() 返回的是 Map


六、counting()

  • 计算流中元素的个数

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Long studentCount = studentList.stream().collect(counting());
    System.out.println(studentCount); // 4
    

七、joining()

  • 连接对流中每个元素调用 toString() 方法后所生成的字符串

  • 该方法有三个参数

    • CharSequence delimiter

      • 每个元素之间的分隔符
    • CharSequence prefix

      • 连接结果的前缀
    • CharSequence suffix

      • 连接结果的后缀
  • 该方法有三个重载方法,为了方便展示我们将参数类型省去

    • joining()

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      String studentJoining = studentList.stream()
          .map(Student::getName).collect(joining());
      System.out.println(studentJoining); // 小白小黑小红小明
      
    • joining(delimiter)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      String studentJoining = studentList.stream()
          .map(Student::getName).collect(joining("@"));
      System.out.println(studentJoining); // 小白@小黑@小红@小明
      
    • joining(delimiter, prefix, suffix)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      String studentJoining = studentList.stream()
          .map(Student::getName).collect(joining("@", "111", "222"));
      System.out.println(studentJoining); // 111小白@小黑@小红@小明222
      

八、toSet()

  • 将流中所有元素收集到一个 Set 中,并去除重复项

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Set<Integer> studentAgeSet = studentList.stream()
        .map(Student::getAge).collect(toSet());
    System.out.println(studentAgeSet); // [20, 21, 22]
    

九、summarizingDouble()

  • 收集流中元素 Double 属性字段的统计值,如:最大值、最小值、总和、平均值

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    DoubleSummaryStatistics doubleSummaryStatistics = studentList
        .stream().collect(summarizingDouble(Student::getScore));
    // DoubleSummaryStatistics{count=4, sum=347.000000, 
    // min=80.000000, average=86.750000, max=95.000000}
    System.out.println(doubleSummaryStatistics);
    

十、summarizingInt()

  • 收集流中元素 Integer 属性字段的统计值,如:最大值、最小值、总和、平均值

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    IntSummaryStatistics intSummaryStatistics = studentList
            .stream().collect(summarizingInt(Student::getAge));
    // IntSummaryStatistics{count=4, sum=85, 
    // min=20, average=21.250000, max=22}
    System.out.println(intSummaryStatistics);
    

十一、summarizingLong()

  • 收集流中元素 Long 属性字段的统计值,如:最大值、最小值、总和、平均值

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    LongSummaryStatistics longSummaryStatistics = studentList
            .stream().collect(summarizingLong(Student::getAge));
    // IntSummaryStatistics{count=4, sum=85,
    // min=20, average=21.250000, max=22}
    System.out.println(longSummaryStatistics);
    

十二、averagingDouble()

  • 计算流中元素 Double 属性字段的平均值

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Double averageScore = studentList.stream().collect(averagingDouble(Student::getScore));
    System.out.println(averageScore); // 86.75
    

十三、averagingInt()

  • 计算流中元素 Integer 属性字段的平均值

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Double averageAge = studentList.stream().collect(averagingInt(Student::getAge));
    System.out.println(averageAge); // 21.25
    

十四、averagingLong()

  • 计算流中元素 Long 属性字段的平均值

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Double averageAge = studentList.stream().collect(averagingLong(Student::getAge));
    System.out.println(averageAge); // 21.25
    

十五、summingDouble()

  • 计算流中元素 Double 属性字段的总和

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Double summingScore = studentList.stream().collect(summingDouble(Student::getScore));
    System.out.println(summingScore); // 347.0
    

十六、summingInt()

  • 计算流中元素 Integer 属性字段的总和

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Integer summingAge = studentList.stream().collect(summingInt(Student::getAge));
    System.out.println(summingAge); // 85
    

十七、summingLong()

  • 计算流中元素 Long 属性字段的总和

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Long summingAge = studentList.stream().collect(summingLong(Student::getAge));
    System.out.println(summingAge); // 85
    

十八、reducing()

  • 从一个作为累加器的初始值开始,将流归约为单个值

  • 该方法有三个参数

    • U identity

      • 初始值
    • Function<? super T, ? extends U> mapper

      • 映射函数,应用于每个输入值
    • BinaryOperator<U> op

      • 计算函数,接收两个参数,返回一个相同类型的值
  • 该方法有三个重载方法,为了方便展示我们将参数类型省去

    • reducing(op)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Optional<Double> reducingScore = studentList.stream()
          .map(Student::getScore).collect(reducing((x, y) -> x + y));
      System.out.println(reducingScore.get()); // 347.0
      
      • 此处我们对学生分数进行归约累加
    • reducing(identity, op)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Double reducingScore = studentList.stream().map(Student::getScore)
          .collect(reducing(3.0, (x, y) -> x + y));
      System.out.println(reducingScore); // 350.0
      
      • 此处我们以 3.0 作为初始值,然后再对学生分数进行规约累加
    • reducing(identity, mapper, op)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Double reducingScore = studentList.stream().map(Student::getScore)
          .collect(reducing(3.0, x -> x * 2, (x, y) -> x + y));
      System.out.println(reducingScore); // 697.0
      
      • 此处我们以 3.0 作为初始值,然后再将学生分数都乘以 2,最后再对学生分数进行规约累加

十九、maxBy()

  • 将流按照给定的比较器筛选出最大元素,用 Optional 进行包裹
    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Optional<Student> studentMaxOptional = studentList.stream()
        .collect(maxBy(comparingDouble(Student::getScore)));
    // Student(name=小黑, age=21, score=95.0)
    System.out.println(studentMaxOptional.get());
    

二十、minBy()

  • 将流按照给定的比较器筛选出最小元素,用 Optional 进行包裹

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    Optional<Student> studentMinOptional = studentList.stream()
        .collect(minBy(comparingDouble(Student::getScore)));
    // Student(name=小红, age=22, score=80.0)
    System.out.println(studentMinOptional.get());
    

二十一、partitioningBy()

  • 根据流中每个元素应用谓词的结果来对元素进行分组 (只会分为 true 和 false 两组)

  • 该方法有两个参数

    • Predicate<? super T> predicate

      • 断言函数,用于对输入元素进行分类
    • Collector<? super T, A, D> downstream

      • 归约收集器
  • 该方法有两个重载方法,为了方便展示我们将参数类型省去

    • partitioningBy(predicate)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Boolean, List<Student>> studentMap  = studentList.stream()
          .collect(partitioningBy(student -> student.getScore() >= 90));
      // {false=[Student(name=小红, age=22, score=80.0), Student(name=小明, age=22, score=82.0)],
      // true=[Student(name=小白, age=20, score=90.0), Student(name=小黑, age=21, score=95.0)]}
      System.out.println(studentMap);
      
      • 此处我们以学生分数 >=90 作为分区条件对集合进行分组
    • partitioningBy(predicate, downstream)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Boolean, Map<Integer, Long>> studentMap = studentList.stream()
          .collect(partitioningBy(student -> student.getScore() >= 90,
          groupingBy(Student::getAge, counting())));
      // {false={22=2}, true={20=1, 21=1}}
      System.out.println(studentMap);
      
      • 此处我们以学生分数 >=90 作为分区条件对集合进行分组,然后再对分组结果进行归约统计

二十二、collectingAndThen()

  • 包裹一个收集器,对其结果应用转换函数

  • 该方法有两个参数

    • Collector<T,A,R> downstream

      • 收集器
    • Function<R,RR> finisher

      • 转换函数
  • 该方法有一个重载方法,为了方便展示我们将参数类型省去

    • collectingAndThen(downstream, finisher)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Integer listSize = studentList.stream()
          .collect(collectingAndThen(toList(), List::size));
      System.out.println(listSize); // 4
      
      • 此处我们将 toList() 方法得到的结果转换为了其长度

二十三、mapping()

  • 将流中的每个元素转换为指定类型的元素

  • 该方法有两个参数

    • Function<? super T, ? extends U> mapper

      • 映射函数,对流中元素进行转换
    • Collector<? super U, A, R> downstream

      • 归约收集器
  • 该方法有一个重载方法,为了方便展示我们将参数类型省去

    • mapping(mapper, downstream)

      List<Student> studentList = new ArrayList<>(Arrays.asList(
              new Student("小白", 20, 90),
              new Student("小黑", 21, 95),
              new Student("小红", 22, 80),
              new Student("小明", 22, 82)));
      
      Map<Integer, ArrayList<String>> mapping = studentList.stream()
          .collect(groupingBy(Student::getAge, mapping(student -> {
              double score = student.getScore();
              String name = student.getName();
              if (score >= 90) {
                  return name + ": 优秀";
              } else if (score >= 80 && score < 90) {
                  return name + ": 良好";
              } else if (score >= 60 && score < 80) {
                  return name + ": 及格";
              } else {
                  return name + ": 不及格";
              }
      }, toCollection(ArrayList::new))));
      // {20=[小白: 优秀], 21=[小黑: 优秀], 22=[小红: 良好, 小明: 良好]}
      System.out.println(mapping);
      
      • 此处我们先按照年龄对学生进行分组,然后再按分数高低分别映射为优秀、良好、及格和不及格,并使用 ArrayList 进行接收

二十四、toCollection()

  • 将流中所有元素收集到指定容器

    List<Student> studentList = new ArrayList<>(Arrays.asList(
            new Student("小白", 20, 90),
            new Student("小黑", 21, 95),
            new Student("小红", 22, 80),
            new Student("小明", 22, 82)));
    
    HashSet<String> studentNameSet = studentList.stream()
        .map(Student::getName).collect(toCollection(HashSet::new));
    // [小明, 小白, 小红, 小黑]
    System.out.println(studentNameSet);