首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java Stream API 收集数据的利器:Collectors 工具类常用源码解读和示例

最近做数据处理,从一个Stream流中通过某些条件收集一些数据组成一个新的集合,最常的一个工具类Collectors,让我们一起看看它。

Collectors 从字面意思可以理解为收集器。在Java中有这么一个约定,带s的一般为工具类,不带s的是接口。因此Collector是接口,而Collectors是工具类。

java.util.stream.Collectors是 Java 提供的一个工具类,常用于处理 Stream 的终端操作,它提供了大量静态方法,帮助将流的数据收集到集合(比如List、Set)、字符串等容器中,同时支持各种数据聚合和分组操作。

一、Collectors常见方法的解读和分析

1. toList

功能:将 Stream 中的元素收集到一个List中。

public static <T> Collector<T, ?, List<T>> toList() {

  return new CollectorImpl<>(

      ArrayList::new,           // Supplier:创建一个新的容器

      List::add,                // Accumulator:向容器添加元素

      (left, right) -> {        // Combiner:合并两个容器

          left.addAll(right);

          return left;

      },

      CH_UNORDERED              // Characteristics:无序

  );

}

解析

使用了ArrayList作为结果容器。

List::add

将流中的元素一个一个加入到ArrayList中。

如果流是并行的,Combiner将合并部分结果。

2. toSet

功能:将 Stream 中的元素收集到一个Set中。

public static <T> Collector<T, ?, Set<T>> toSet() {

  return new CollectorImpl<>(

      HashSet::new,           // 使用 HashSet 作为容器

      Set::add,               // 添加元素

      (left, right) -> {      // 合并两个 HashSet

          left.addAll(right);

          return left;

      },

      CH_UNORDERED            // 无序

  );

}

解析

toSet

的逻辑和toList类似,但容器换成了HashSet。

保证集合中元素唯一性。

3. joining

功能:将流中的字符串元素连接成一个字符串。

public static Collector<CharSequence, ?, String> joining() {

  return new CollectorImpl<>(

      StringBuilder::new,          // 使用 StringBuilder 作为容器

      StringBuilder::append,       // 将流中的每个元素追加到容器

      (left, right) -> {           // 合并两个 StringBuilder

          left.append(right);

          return left;

      },

      StringBuilder::toString,     // 最终将 StringBuilder 转换为 String

      CH_NOID

  );

}

解析

利用StringBuilder的高效拼接能力。

特别适合处理大量字符串拼接的场景。

扩展方法:

joining(CharSequence delimiter):支持自定义分隔符

joining(CharSequence delimiter,CharSequence prefix, CharSequence suffix):支持自定义前缀和后缀。

4. groupingBy

功能:基于分类函数对流中的元素分组,结果存储在一个Map中。

public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(

      Function<? super T, ? extends K> classifier) {

  return groupingBy(classifier, toList());

}

解析

classifier

是一个分类函数,用于生成Map的键。

值默认存储为一个List,可通过重载方法自定义存储容器。

复杂版本:

public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(

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

      Collector<? super T, A, D> downstream) {

  return groupingBy(classifier, HashMap::new, downstream);

}

5. partitioningBy

功能:基于谓词将流分为两个组。

public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {

  return partitioningBy(predicate, toList());

}

解析

根据谓词的结果将流分为true和false两组。

默认使用List存储分组结果。

6. summarizingInt / summarizingDouble / summarizingLong

功能:对数值类型进行汇总统计。

public static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {

  return new CollectorImpl<>(

      IntSummaryStatistics::new,  // 创建统计容器

      (r, t) -> r.accept(mapper.applyAsInt(t)),  // 将元素加入统计

      (l, r) -> { l.combine(r); return l; },    // 合并两个统计

      CH_NOID

  );

}

解析

IntSummaryStatistics 提供统计结果:最大值、最小值、总和、计数、平均值。流适合数值类型时直接使用。

二、Collectors 常用方法的应用

1. 使用 toList 收集元素到列表

将流中的元素收集到List中:

import java.util.*;

import java.util.stream.*;

publicclass CollectorExample {

  public static void main(String[] args) {

      List<String> names = Arrays.asList("John", "Paul", "George", "Ringo");

      // 使用 toList 将流中的元素收集到 List 中

      List<String> collectedNames = names.stream().filter(name -> name.startsWith("J")).collect(Collectors.toList());

      System.out.println(collectedNames);  // 输出: [John]

  }

}

解释

使用toList()将流中的元素按条件(以 J 开头)收集到一个List中。

2. 使用 toSet 收集元素到集合

将流中的元素收集到Set中,去除重复元素:

import java.util.*;

import java.util.stream.*;

public class CollectorExample {

  public static void main(String[] args) {

      List<String> names = Arrays.asList("John", "Paul", "George", "Ringo", "John");

      // 使用 toSet 将流中的元素收集到 Set 中

      Set<String> collectedNames = names.stream().collect(Collectors.toSet());

      System.out.println(collectedNames);  // 输出: [John, Paul, George, Ringo]

  }

}

解释

使用toSet()将流中的元素收集到一个Set中,自动去重。

3. 使用 joining 将字符串连接成一个字符串

将流中的字符串连接成一个单一的字符串:

import java.util.*;

import java.util.stream.*;

public class CollectorExample {

  public static void main(String[] args) {

      List<String> words = Arrays.asList("Java", "Stream", "Collectors");

      // 使用 joining 将流中的字符串连接成一个字符串

      String result = words.stream().collect(Collectors.joining(" ", "(", ")"));

      System.out.println(result);  // 输出: (Java Stream Collectors)

  }

}

解释

使用joining方法将字符串流连接成一个大字符串," "是分隔符,"("和")"是前后缀。

4. 使用 groupingBy 分组

按条件分组流中的元素,返回一个Map:

import java.util.*;

import java.util.stream.*;

publicclass CollectorExample {

  public static void main(String[] args) {

      List<String> names = Arrays.asList("John", "Jane", "Tom", "Jerry", "Bob", "Alice");

      // 按名字的首字母分组

      Map<Character, List<String>> groupedByFirstLetter = names.stream().collect(Collectors.groupingBy(name -> name.charAt(0)));

      System.out.println(groupedByFirstLetter);

      // 输出: {J=[John, Jane, Jerry], T=[Tom], B=[Bob], A=[Alice]}

  }

}

解释

groupingBy

按照名字的首字母将List中的字符串分组,返回一个Map<Character, List<String>>。

5. 使用 partitioningBy 分割流

将流中的元素按照某个条件分为两组,返回一个Map<Boolean, List<T>>:

import java.util.*;

import java.util.stream.*;

publicclass CollectorExample {

  public static void main(String[] args) {

      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

      // 按奇偶数分组

      Map<Boolean, List<Integer>> partitioned = numbers.stream().collect(Collectors.partitioningBy(n -> n % 2 == 0));

      System.out.println(partitioned);

      // 输出: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8]}

  }

}

解释

使用partitioningBy按照元素是否为偶数将流中的整数分为两组。

6. 使用 summarizingInt 进行统计

对数值类型进行统计,返回一个IntSummaryStatistics:

import java.util.*;

import java.util.stream.*;

publicclass CollectorExample {

  public static void main(String[] args) {

      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

      // 统计所有整数的数量、总和、平均值、最大值、最小值

      IntSummaryStatistics stats = numbers.stream().collect(Collectors.summarizingInt(Integer::intValue));

      System.out.println(stats);

      // 输出: IntSummaryStatistics{count=9, sum=45, min=1, average=5.000000, max=9}

  }

}

解释

使用summarizingInt统计整型数据的数量、总和、最小值、最大值和平均值。

7. 使用 reducing 进行归约

使用reducing方法将流中的元素归约为一个单一的值:

import java.util.*;

import java.util.stream.*;

public class CollectorExample {

  public static void main(String[] args) {

      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

      // 计算所有数字的和

      int sum = numbers.stream().collect(Collectors.reducing(0, Integer::sum));

      System.out.println(sum);  // 输出: 15

  }

}

解释

使用reducing归约流中的整数,初始值为0,每次通过Integer::sum累加计算总和。

8. 使用 counting 计算元素数量

统计流中元素的数量:

List<String> names = Arrays.asList("John", "Paul", "George", "Ringo");

// 统计流中元素的数量

long count = names.stream().collect(Collectors.counting());

System.out.println(count);  // 输出: 4

解释

使用counting统计流中元素的数量,返回一个long类型的值。

三、Collectors 的优缺点

1、Collectors 的优点

简洁性:Collectors提供了许多方便的静态方法,减少了手动编写收集逻辑的代码,让操作流变得非常简单。

高效性:这些方法是经过优化的,能够在处理大量数据时依然保持较好的性能。

多样性:可以非常灵活地对数据进行收集、分组、统计等操作,满足不同的需求。

函数式编程支持:与 Stream API 配合使用,充分体现了 Java 8 引入的函数式编程特性,代码更加简洁易懂。

2、Collectors 的缺点

可读性问题:对于复杂的 Collector链式调用,特别是在不熟悉流式操作的情况下,可能会使代码不太容易理解。

性能消耗:虽然大多数操作经过优化,但在某些情况下,比如频繁地调用 groupingBy 或 joining,可能会产生较高的内存开销和性能问题。

不适合所有场景:在某些非常简单或特定的数据处理场景中,使用流和 Collectors 可能反而增加了代码的复杂度,传统的 for 循环可能更加高效和直观。

四、最后总结

Collectors 是 Java 8 中一个非常强大的工具类,它让我们能以非常优雅和高效的方式对流进行收集和处理。通过理解并熟练使用这些常见方法,我们能让代码变得更加简洁,并能轻松实现一些常见的数据处理任务。不过,还是要根据实际的应用场景来决定是否使用流和 Collectors,不盲目追求流式编程。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OKqP4Ze9lh5YsJjgXYLc0RHw0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券