最近做数据处理,从一个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,不盲目追求流式编程。
领取专属 10元无门槛券
私享最新 技术干货