Java 8 引入的 Collectors 类是一个工具类,提供了一系列预定义的收集器实现,用于简化流(Stream)的收集操作。它具有以下优势:简化和统一了收集操作,提高了代码的可读性。Collectors 常用的静态方法包括将元素收集到集合(如 List、Set)、转换为其他类型(如 Map)、进行聚合元素操作(如计数、求和、平均值)、查找最大值和最小值、分组、分区以及连接字符串等。通过这些方法,可以轻松实现复杂的流处理逻辑,大大提高了开发效率。
目录
前置知识:Stream API
可参考 Stream API 详解和25道练习题-CSDN博客
一、什么是 Collectors
Collectors 是 Java 8 及更高版本中引入的一个工具类,它提供了许多静态方法,这些方法返回实现了 Collector 接口的实例。Collector 接口定义了如何将流(Stream)中的元素累积成一个结果容器,这个结果容器可以是任何最终结果类型,例如一个集合、一个值或者一个映射。
二、Collectors 的优势
- 简化和统一了收集操作:在 Java 8 之前,将流(Stream)中的元素收集到集合或执行其他终端操作需要编写大量的样板代码。Collectors 提供了预定义的收集器,使得这些操作变得简单和统一。
- 提高了代码的可读性:使用 Collectors 可以用一行代码替换原本需要多行代码的收集逻辑,这使得代码更加简洁和易于理解。
三、Collectors 常用的静态方法
// 将元素收集到集合中
- Collectors.toList()
- Collectors.toSet()
- Collectors.toCollection(Supplier<Collection<? super T>> collectionFactory)
// 转换为其他类型
- Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
- Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
- Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
- Collectors.toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
- Collectors.toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
- Collectors.toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
- Collectors.collectingAndThen(Collector<T, A, R> downstream, Function<R,RR> finisher)
// 聚合元素
- Collectors.counting()
- Collectors.summingInt(ToIntFunction<? super T> mapper)
- Collectors.summingLong(ToLongFunction<? super T> mapper)
- Collectors.summingDouble(ToDoubleFunction<? super T> mapper)
- Collectors.averagingInt(ToIntFunction<? super T> mapper)
- Collectors.averagingLong(ToLongFunction<? super T> mapper)
- Collectors.averagingDouble(ToDoubleFunction<? super T> mapper)
- Collectors.reducing(T identity, BinaryOperator<T> accumulator)
- Collectors.reducing(BinaryOperator<T> accumulator)
- Collectors.reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> accumulator)
// 最大值、最小值
- Collectors.maxBy(Comparator<? super T> comparator)
- Collectors.minBy(Comparator<? super T> comparator)
// 分组
- Collectors.groupingBy(Function<? super T, ? extends K> classifier)
- Collectors.groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
- Collectors.groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)
// 分区
- Collectors.partitioningBy(Predicate<? super T> predicate)
- Collectors.partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
// 连接字符串
- Collectors.joining()
- Collectors.joining(CharSequence delimiter)
- Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
1. 将元素收集到集合中
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> strings = Arrays.asList("a", "b", "c", "a", "b", "c");
// 收集到List
List<String> list = strings.stream().collect(Collectors.toList());
//List<String> list = new ArrayList<>(strings);
System.out.println(list);//[a, b, c, a, b, c]
// 收集到Set
Set<String> set = strings.stream().collect(Collectors.toSet());
//Set<String> set = new HashSet<>(strings);
System.out.println(set);//[a, b, c]
// 收集到指定的集合类型,如ArrayList, LinkList, HashSet, TreeSet, PriorityQueue等
ArrayList<String> arrayList = strings.stream().collect(Collectors.toCollection(ArrayList::new));
//ArrayList<String> arrayList = new ArrayList<>(strings);
System.out.println(arrayList);//[a, b, c, a, b, c]
}
}
2. 转换为其他类型
(1)Collectors.toMap
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
Map<String, Integer> map = people.stream()
.collect(Collectors.toMap(
person -> person.getName(), // 键映射器
person -> person.getAge() // 值映射器
));
System.out.println(map); // 输出:{Bob=30, Alice=25, Charlie=35}
// people.add(new Person("Charlie", 33)); // 再插入重复的键会抛出异常
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
(2)Collectors.toMap 有合并函数
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Alice", 30),
new Person("Alice", 35)
); // 注意有相同的名字
Map<String, Integer> map = people.stream()
.collect(Collectors.toMap(
Person::getName, // 键映射器
Person::getAge, // 值映射器
(age1, age2) -> age1 + age2 // 合并函数,遇到重复的键则合并
));
System.out.println(map); // 输出:{Alice=55}
}
}
class Person {
// 同上...
}
(3)Collectors.toMap 有合并函数和映射供应器
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = List.of(
new Person("David", 20),
new Person("Alice", 25),
new Person("Alice", 35),
new Person("Bob", 30)
);
Map<String, Integer> map = people.stream()
.collect(Collectors.toMap(
Person::getName, // 键映射器
Person::getAge, // 值映射器
(existingValue, newValue) -> existingValue, // 合并函数,这里选择保留旧值
TreeMap::new // 映射供应器,这里使用 TreeMap 来保持键的顺序
));
System.out.println(map); // 输出:{Alice=25, Bob=30, David=20} 字母序递增
}
}
class Person {
// 同上...
}
(4)Collectors.toConcurrentMap
Collectors.toConcurrentMap:
- 线程安全:生成的 ConcurrentMap 设计用于在多线程环境中使用,它提供了足够的线程安全性和并发性能。
- 默认映射供应器:如果没有提供特定的映射供应器,则默认使用 ConcurrentHashMap。
Collectors.toMap:
- 非线程安全:生成的Map在多线程环境中可能会遇到并发修改问题。
- 默认映射供应器:如果没有提供特定的映射供应器,则默认使用 HashMap。
以下为Collectors.toConcurrentMap的简单示例,Collectors.toConcurrentMap 有合并函数和映射供应器参考上面的(2)和(3)。
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30)
);
ConcurrentMap<String, Integer> concurrentMap = people.stream()
.collect(Collectors.toConcurrentMap(Person::getName, Person::getAge));
System.out.println(concurrentMap); // {Bob=30, Alice=25}
}
}
class Person {
// 同上...
}
(5)Collectors.collectingAndThen
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
// 使用 collectingAndThen 来收集名字到一个列表,然后对其进行排序
List<String> sortedNames = people.stream()
.map(Person::getName) // 将 Person 对象映射到它们的名字
.collect(Collectors.collectingAndThen(
Collectors.toList(), // 下游收集器,先将名字收集到列表中
list -> { // 然后执行排序操作
list.sort(String::compareTo); // 完成函数,对列表进行排序
return list;
}
));
System.out.println(sortedNames); // 输出:[Alice, Bob, Charlie]
}
}
class Person {
// 同上...
}
3. 聚合元素
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
// Collectors.counting() 统计元素个数
long count = people.stream().collect(Collectors.counting());
// 或 people.size();
System.out.println("人数: " + count); // 输出:人数: 3
// Collectors.summingInt(ToIntFunction<? super T> mapper) 对整数属性求和
int totalAge = people.stream().collect(Collectors.summingInt(Person::getAge));
// 或 people.stream().mapToInt(Person::getAge).sum();
System.out.println("总年龄: " + totalAge); // 输出:总年龄: 90
// Collectors.summingLong(ToLongFunction<? super T> mapper) 对长整数属性求和
long totalAgeLong = people.stream().collect(Collectors.summingLong(p -> (long) p.getAge()));
// 或 people.stream().mapToLong(p -> (long) p.getAge()).sum();
System.out.println("总年龄(长整数): " + totalAgeLong); // 输出:总年龄(长整数): 90
// Collectors.summingDouble(ToDoubleFunction<? super T> mapper) 对双精度浮点数属性求和
double totalAgeDouble = people.stream().collect(Collectors.summingDouble(p -> (double) p.getAge()));
// 或 people.stream().mapToDouble(p -> (double) p.getAge()).sum();
System.out.println("总年龄(双精度): " + totalAgeDouble); // 输出:总年龄(双精度): 90.0
// Collectors.averagingInt(ToIntFunction<? super T> mapper) 计算整数属性的平均值
double averageAge = people.stream().collect(Collectors.averagingInt(Person::getAge));
System.out.println("平均年龄: " + averageAge); // 输出:平均年龄: 30.0
// Collectors.averagingLong(ToLongFunction<? super T> mapper) 计算长整数属性的平均值
double averageAgeLong = people.stream().collect(Collectors.averagingLong(p -> (long) p.getAge()));
System.out.println("平均年龄(长整数): " + averageAgeLong); // 输出:平均年龄(长整数): 30.0
// Collectors.averagingDouble(ToDoubleFunction<? super T> mapper) 计算双精度浮点数属性的平均值
double averageAgeDouble = people.stream().collect(Collectors.averagingDouble(p -> (double) p.getAge()));
System.out.println("平均年龄(双精度): " + averageAgeDouble); // 输出:平均年龄(双精度): 30.0
// Collectors.reducing(T identity, BinaryOperator<T> accumulator) 使用提供的初始值和累加器进行归约
String names = people.stream().map(Person::getName).collect(Collectors.reducing("", (s1, s2) -> s1 + ", " + s2));
// 或 people.stream().map(Person::getName).reduce("", (s1, s2) -> s1 + ", " + s2);
System.out.println("名字列表: " + names); // 输出:名字列表: , Alice, Bob, Charlie
// Collectors.reducing(BinaryOperator<T> accumulator) 不提供初始值,返回 Optional
Optional<String> firstAndLast = people.stream().map(Person::getName).collect(Collectors.reducing((s1, s2) -> s1 + ", " + s2));
// 或 people.stream().map(Person::getName).reduce((s1, s2) -> s1 + ", " + s2);
firstAndLast.ifPresent(s -> System.out.println("名字列表(无初始值): " + s)); // 输出:名字列表(无初始值): Alice, Bob, Charlie
// Collectors.reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> accumulator) 使用映射器和累加器进行归约
Person reducedPerson = people.stream()
.collect(Collectors.reducing(
new Person("", 0), // 初始值
person -> new Person(person.getName(), person.getAge()+1), // 映射器
(p1, p2) -> new Person(p1.getName() + ", " + p2.getName(), p1.getAge() + p2.getAge()) // 累加器
));
System.out.println("归约后的 Person 对象: " + reducedPerson.getName() + " --- " + reducedPerson.getAge());
// 输出:归约后的 Person 对象: Alice, Bob, Charlie --- 93
}
}
class Person {
// 同上...
}
4. 最大值、最小值
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
// 使用 Collectors.maxBy 找到年龄最大的 Person
Optional<Person> oldest = people.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
// 或people.stream().max(Comparator.comparingInt(Person::getAge));
oldest.ifPresent(person -> System.out.println("年龄最大的 Person: " + person.getName() + " - " + person.getAge()));
// 输出:年龄最大的 Person: Charlie - 35
// 使用 Collectors.minBy 找到年龄最小的 Person
Optional<Person> youngest = people.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
// 或people.stream().min(Comparator.comparingInt(Person::getAge));
youngest.ifPresent(person -> System.out.println("年龄最小的 Person: " + person.getName() + " - " + person.getAge()));
// 输出:年龄最小的 Person: Alice - 25
}
}
class Person {
// 同上...
}
5. 分组
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 25),
new Person("David", 30),
new Person("Eve", 35)
);
// 使用 Collectors.groupingBy 分组并计数
Map<Integer, Long> peopleByAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
System.out.println("按年龄分组的人数: " + peopleByAge);
// 输出:按年龄分组的人数: {35=1, 25=2, 30=2}
// 使用 Collectors.groupingBy 分组并计算平均年龄
Map<Integer, Double> averageAgeByGroup = people.stream()
.collect(Collectors.groupingBy(Person::getAge, Collectors.averagingInt(Person::getAge)));
System.out.println("每个年龄组的平均年龄: " + averageAgeByGroup);
// 输出:每个年龄组的平均年龄: {35=35.0, 25=25.0, 30=30.0}
// 使用 Collectors.groupingBy 分组,并提供自定义的 mapFactory
Map<Integer, List<Person>> peopleByAgeWithCustomMap = people.stream()
.collect(Collectors.groupingBy(Person::getAge, () -> new LinkedHashMap<>(), Collectors.toList()));
System.out.println("按年龄分组的人员列表(使用自定义 mapFactory): " + peopleByAgeWithCustomMap);
// 输出:按年龄分组的人员列表(使用自定义 mapFactory): {25=[Alice, Charlie], 30=[Bob, David], 35=[Eve]}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return name;
}
}
6. 分区
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35),
new Person("David", 40),
new Person("Eve", 28)
);
// 使用 Collectors.partitioningBy 根据 age > 30 分组
Map<Boolean, List<Person>> partitionedByAge = people.stream()
.collect(Collectors.partitioningBy(person -> person.getAge() > 30));
System.out.println("根据年龄是否大于30分组: " + partitionedByAge);
// 输出:根据年龄是否大于30分组: {false=[Alice, Eve], true=[Bob, Charlie, David]}
// 使用 Collectors.partitioningBy 根据 age > 30 分组,并计算每组的平均年龄
Map<Boolean, Double> averageAgeByPartition = people.stream()
.collect(Collectors.partitioningBy(person -> person.getAge() > 30,
Collectors.averagingInt(Person::getAge)));
System.out.println("每组的平均年龄: " + averageAgeByPartition);
// 输出:每组的平均年龄: {false=26.5, true=36.0}
}
}
class Person{
// 同上...
}
7. 连接字符串
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice",19),
new Person("Bob",20),
new Person("Charlie",22)
);
// 使用 Collectors.joining() 连接名字,不添加分隔符
String names = people.stream()
.map(Person::getName)
.collect(Collectors.joining());
System.out.println("连接名字(无分隔符): " + names);
// 输出:连接名字(无分隔符): AliceBobCharlie
// 使用 Collectors.joining(CharSequence delimiter) 连接名字,添加分隔符
String namesWithComma = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", "));
System.out.println("连接名字(添加逗号分隔符): " + namesWithComma);
// 输出:连接名字(添加逗号分隔符): Alice, Bob, Charlie
// 使用 Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 连接名字,添加分隔符、前缀和后缀
String namesWithPrefixSuffix = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", ", "Names = [", "]"));
//.collect(Collectors.collectingAndThen(Collectors.joining(", "), s -> "[" + s + "]"));
System.out.println("连接名字(添加分隔符、前缀和后缀): " + namesWithPrefixSuffix);
// 输出:连接名字(添加分隔符、前缀和后缀): Names = [Alice, Bob, Charlie]
}
}
class Person {
// 同上...
}