引言
在 Java 的 Stream API 中,Collectors 是一个非常强大的工具类,它提供了许多静态方法,用于将 Stream 的元素收集到集合、字符串或其他类型的结果中。使用 Collectors,我们可以轻松地进行数据聚合和转换操作。
文章目录
- 引言
- 什么是 Collectors?
- 常用 Collectors 方法详解
- toList()
- toSet()
- toMap()
- joining()
- groupingBy()
- partitioningBy()
- summarizingInt()
- reducing()
- counting()
- 高级用法
- 嵌套收集器
- 自定义收集器
- 注意事项
什么是 Collectors?
Collectors 类是 Java 中的一个实用工具类,包含了一系列静态方法,这些方法用于创建各种常见的集合操作。它们用于将流中的元素累积到某些结果中,例如 List、Set、Map 等,还可以进行各种统计操作,如求和、平均、最小值、最大值等。
Collectors 类位于 java.util.stream 包中,通常与 Stream 的 collect() 方法一起使用。它的设计遵循了函数式编程的原则,使得数据处理变得更加简洁和高效。
常用 Collectors 方法详解
下面将详细介绍一些常用的 Collectors 方法,并通过通俗易懂的示例来展示它们的使用。
toList()
toList() 方法是最简单的收集器,它将流中的元素收集到一个 List 中。这是一个无参数方法,返回一个新的 List。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CollectorsToListExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
List<String> result = fruits.stream().collect(Collectors.toList());
System.out.println(result); // 输出 [apple, banana, cherry]
}
}
在这个例子中,将一个包含水果名称的 List 转换为一个 Stream,并使用 Collectors.toList()
方法将其收集回一个 List。
toSet()
toSet() 方法将流中的元素收集到一个 Set 中,自动去除重复项。这也是一个无参数方法,返回一个新的 Set。
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class CollectorsToSetExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apple");
Set<String> result = fruits.stream().collect(Collectors.toSet());
System.out.println(result); // 输出 [banana, cherry, apple]
}
}
在这个例子中,有一个包含重复元素的 List,通过 Collectors.toSet()
方法,我们将其收集到一个 Set 中,从而去除了重复元素。
toMap()
toMap() 方法需要两个函数:一个用于生成键,另一个用于生成值。它将流中的元素收集到一个 Map 中。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CollectorsToMapExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
Map<String, Integer> result = fruits.stream().collect(Collectors.toMap(
fruit -> fruit, // 键映射函数
String::length // 值映射函数
));
System.out.println(result); // 输出 {banana=6, cherry=6, apple=5}
}
}
在这个例子中,将水果名称作为键,水果名称的长度作为值,收集到一个 Map 中。
joining()
joining() 方法可以将流中的字符串元素连接成一个单一的字符串。它可以有三个参数:分隔符、前缀和后缀。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CollectorsJoiningExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
String result = fruits.stream().collect(Collectors.joining(", ", "[", "]"));
System.out.println(result); // 输出 [apple, banana, cherry]
}
}
在这个例子中,使用 Collectors.joining() 方法将水果名称连接成一个字符串,并指定了逗号作为分隔符,方括号作为前缀和后缀。
groupingBy()
groupingBy() 方法根据提供的函数对流中的元素进行分组,返回一个 Map,其中键是分组函数的结果,值是符合该分组的元素列表。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CollectorsGroupingByExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apricot");
Map<Character, List<String>> result = fruits.stream().collect(Collectors.groupingBy(fruit -> fruit.charAt(0)));
System.out.println(result); // 输出 {a=[apple, apricot], b=[banana], c=[cherry]}
}
}
在这个例子中,按水果名称的首字母对水果进行分组。
partitioningBy()
partitioningBy() 方法根据提供的谓词对流中的元素进行分区,返回一个 Map,其中键是布尔值,值是符合或不符合谓词的元素列表。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CollectorsPartitioningByExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apricot");
Map<Boolean, List<String>> result = fruits.stream().collect(Collectors.partitioningBy(fruit -> fruit.startsWith("a")));
System.out.println(result); // 输出 {false=[banana, cherry], true=[apple, apricot]}
}
}
在这个例子中,根据水果名称是否以字母 ‘a’ 开头将水果分为两个部分。
summarizingInt()
summarizingInt() 方法接收一个将对象转换为 int 的函数,并返回一个 IntSummaryStatistics 对象,其中包含元素的数量、总和、最小值、平均值和最大值。
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
public class CollectorsSummarizingIntExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
IntSummaryStatistics stats = fruits.stream().collect(Collectors.summarizingInt(String::length));
System.out.println(stats); // 输出 IntSummaryStatistics{count=3, sum=17, min=5, average=5.666667, max=6}
}
}
在这个例子中,统计了水果名称的长度信息。
reducing()
reducing() 方法是一个通用的归约操作,它可以将流中的元素归约成一个值。它需要三个参数:初始值、累加器和组合器。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CollectorsReducingExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
int totalLength = fruits.stream().collect(Collectors.reducing(0, String::length, Integer::sum));
System.out.println(totalLength); // 输出 17
}
}
在这个例子中,将水果名称的长度进行归约操作,计算它们的总长度。
counting()
counting() 是一个简单的归约操作,用于计算流中元素的数量。
long count = list.stream().collect(Collectors.counting());
高级用法
除了上述基本用法外,Collectors 还提供了一些高级用法,可以实现更复杂的数据处理需求。
嵌套收集器
Collectors 允许嵌套使用,这意味着我们可以在一个收集操作中组合多个收集器。例如,我们可以先按照某个条件分组,然后对每个组应用另一个收集器。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class NestedCollectorsExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
Map<Integer, Long> lengthCounts = fruits.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
System.out.println(lengthCounts); // 输出 {4=1, 5=2, 6=1, 10=1}
}
}
在这个例子中,我们首先按照字符串长度分组,然后对每个组计数。
自定义收集器
虽然 Collectors 类提供了许多预定义的收集器,但有时我们可能需要创建自定义的收集器来满足特定需求。我们可以通过实现 Collector 接口来创建自定义收集器。
Collector<T, A, R> 是一个泛型接口,其中:
- T 是要收集的元素类型
- A 是累加器的类型(中间结果)
- R 是最终结果的类型
这个 Collector 接口定义了五个方法:
- supplier(): 提供一个初始的空累加器
- accumulator(): 定义如何将新元素添加到累加器中
- combiner(): 定义如何合并两个累加器
- finisher(): 定义如何从累加器得到最终结果
- characteristics(): 定义收集器的特征
收集器的特征就像是收集器的"说明书"。它告诉Java这个收集器有什么特别之处,可以怎么用。比如,“CONCURRENT"特征就像说"多人可以同时使用”,这样Java就知道可以让多个线程一起工作。“UNORDERED"特征就像说"不用排队”,Java就知道不需要保持元素的顺序。“IDENTITY_FINISH"特征就像说"装好就直接用”,Java就知道不用再额外处理结果。有了这些"说明",Java就能更聪明地使用收集器,让程序运行得更快更好。这就像你在使用一个工具时,了解了它的特点,就能更好地发挥它的作用。
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
public class CustomCollectorExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
String result = fruits.stream().collect(new CustomJoiningCollector());
System.out.println(result); // 输出 "APPLE, BANANA, CHERRY, DATE"
}
static class CustomJoiningCollector implements Collector<String, StringBuilder, String> {
@Override
public Supplier<StringBuilder> supplier() {
return StringBuilder::new;
}
@Override
public BiConsumer<StringBuilder, String> accumulator() {
return (sb, s) -> {
if (sb.length() > 0) sb.append(", ");
sb.append(s.toUpperCase());
};
}
@Override
public BinaryOperator<StringBuilder> combiner() {
return StringBuilder::append;
}
@Override
public Function<StringBuilder, String> finisher() {
return StringBuilder::toString;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
}
这个自定义收集器将字符串转换为大写并用逗号分隔。
注意事项
- 在使用 Collectors 的 toMap() 方法时,如果流中存在相同的键,则默认会抛出
IllegalStateException
。可以通过提供合并函数来处理键冲突。 - 当使用并行流时,需要注意线程安全问题。虽然 Collectors 的大多数方法在并行流中都是安全的,但在收集到特定的集合时,可能需要使用线程安全的集合或者在收集操作后进行同步。
- 对于大型数据集,使用 Collectors 的某些方法(如 toList、toSet)可能不如直接使用特定类型的集合(如 ArrayList、HashSet)高效。此外,groupingBy 和 partitioningBy 在处理大型数据集时可能会消耗较多内存。
- 在需要去重时选择 toSet() 而不是 toList()。