使用Collector
作为终端操作
使用Collector
收集stream流元素
最常使用的收集stream流元素到List
:collect(Collectors.toList())
The Collector API is handled differently in the
Stream
interface and in the specialized streams of numbers:IntStream
,LongStream
, andDoubleStream
. TheStream
interface has two overloads of thecollect()
method, whereas the streams of numbers have only one. The missing one is precisely the one that takes a collector object as an argument. So you cannot use a collector object with a specialized stream of numbers.
收集到集合
Collectors
有3个方法收集stream流中的元素到Collection
接口
-
toList()
-
toSet()
-
toCollection()
有2个特殊方法收集到不可变的List
和Set
-
toUnmodifiableList()
-
toUnmodifiableSet()
示例1
List<Integer> numbers =
IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList());
System.out.println("numbers = " + numbers);
结果
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
示例2
Set<Integer> evenNumbers =
IntStream.range(0, 10)
.map(number -> number / 2)
.boxed()
.collect(Collectors.toSet());
System.out.println("evenNumbers = " + evenNumbers);
结果
evenNumbers = [0, 1, 2, 3, 4]
示例3
LinkedList<Integer> linkedList =
IntStream.range(0, 10)
.boxed()
.collect(Collectors.toCollection(LinkedList::new));
System.out.println("linked listS = " + linkedList);
结果
linked list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
使用Collector
计数
Collectors.counting()
等于count()
This is worth noting, and you may be wondering why such a feature has been implemented twice with two different patterns. This question is answered in the next section about collecting in maps where you will be combining collectors to create more collectors.
Collection<String> strings = List.of("one", "two", "three");
long count = strings.stream().count();
long countWithACollector = strings.stream().collect(Collectors.counting());
System.out.println("count = " + count);
System.out.println("countWithACollector = " + countWithACollector);
结果
count = 3
countWithACollector = 3
收集到单个字符串
Collectors.joining()
收集字符串stream流拼接到单个字符串。
示例1
String joined =
IntStream.range(0, 10)
.boxed()
.map(Object::toString)
.collect(Collectors.joining());
System.out.println("joined = " + joined);
结果
joined = 0123456789
示例2
String joined =
IntStream.range(0, 10)
.boxed()
.map(Object::toString)
.collect(Collectors.joining(", "));
System.out.println("joined = " + joined);
结果
joined = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
示例3
String joined =
IntStream.range(0, 10)
.boxed()
.map(Object::toString)
.collect(Collectors.joining(", ", "{", "}"));
System.out.println("joined = " + joined);
结果
joined = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
注意当只有少量的字符串元素需要拼接时,可以使用String.join()
或者StringJoiner
,避免创建stream流的开销。
使用Predicate
分区元素(收集到Map
)
Collector API提供了3中模式从stream流中的元素创建Map
。第一种模式是使用partitionningBy()
创建布尔键Map<Boolean, List<T>>
。
示例
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Boolean, List<String>> map =
strings.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 4));
map.forEach((key, value) -> System.out.println(key + " :: " + value));
结果
false :: [one, two, four, five, six, nine, ten]
true :: [three, seven, eight, eleven, twelve]
Collectors.partitioningBy()
等重载方法下面介绍。
使用groupingBy()
收集到Map
The second collector we present is very important because it allows you to create histograms.
stream流中的元素分组到Map
中
A key is computed for each element of the stream by applying an instance of
Function
to it. This function is provided as an argument of thegroupingBy()
method. It is called a classifier in the Collector API.
There is no restriction on this function apart from the fact that it should not return null.
示例
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Integer, List<String>> map =
strings.stream()
.collect(Collectors.groupingBy(String::length));
map.forEach((key, value) -> System.out.println(key + " :: " + value));
结果
3 :: [one, two, six, ten]
4 :: [four, five, nine]
5 :: [three, seven, eight]
6 :: [eleven, twelve]
对groupingBy()
收集的Map
中的List
values进行后置处理
计算Map
中List
个数
The
groupingBy()
method also accepts another argument, which is another collector. This collector is called a downstream collector in the Collector API, but it is just a regular collector. What makes it a downstream collector is the fact that it is passed as an argument to the creation of another collector.
This downstream collector is used to collect the values of the map created by the
groupingBy()
collector.
示例
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Integer, Long> map =
strings.stream()
.collect(
Collectors.groupingBy(
String::length,
Collectors.counting()));
map.forEach((key, value) -> System.out.println(key + " :: " + value));
结果
3 :: 4
4 :: 3
5 :: 3
6 :: 2
拼接Map
中的List
字符串
示例
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Integer, String> map =
strings.stream()
.collect(
Collectors.groupingBy(
String::length,
Collectors.joining(", ")));
map.forEach((key, value) -> System.out.println(key + " :: " + value));
结果
3 :: one, two, six, ten
4 :: four, five, nine
5 :: three, seven, eight
6 :: eleven, twelve
控制实例Map
The last overload of this
groupingBy()
method takes an instance of aSupplier
as an argument to give you control on which instance ofMap
you need this collector to create.
使用toMap()
收集到Map
这也是第二种模式创建Map
。
Collectors.toMap()
接收2个Function
参数
-
第一个是创建key的映射
-
第二个是创建value的映射
This collector is not used in the same cases as the
Collectors.groupingBy()
. In particular, it does not handle the case where several elements of your stream generate the same key. In that case, by default, anIllegalStateException
is raised.
example
List<User> users = ...;
Map<Long, User> userCache =
users.stream()
.collect(User::getPrimaryKey,
Function.idendity());
当key出现冲突,可以调用重载toMap()
,添加BinaryOperator
参数处理冲突
example
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Integer, String> map =
strings.stream()
.collect(
Collectors.toMap(
element -> element.length(),
element -> element,
(element1, element2) -> element1 + ", " + element2));
map.forEach((key, value) -> System.out.println(key + " :: " + value));
In this example, the three arguments passed to the toMap()
method are the following:
element -> element.length()
is the key mapper.element -> element
is the value mapper.(element1, element2) -> element1 + ", " + element2)
is the merge function, called with the two elements that have generated the same key.
result
3 :: one, two, six, ten
4 :: four, five, nine
5 :: three, seven, eight
6 :: eleven, twelve
As for the
groupingBy()
collector, you can pass a supplier as an argument to thetoMap()
method to control what instance of theMap
interface this collector will use.
The
toMap()
collector has a twin method,toConcurrentMap()
that will collect your data in a concurrent map. The exact type of the map is not guaranteed by the implementation.
从直方图中提取最大值
The
groupingBy()
collector is your best pattern to compute histograms on the data you need to analyze. Let us examine a complete example where you build a histogram and then try to find the maximum value in it based a certain criterion.
提取一个明确的最大值
example
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Integer, Long> histogram =
strings.stream()
.collect(
Collectors.groupingBy(
String::length,
Collectors.counting()));
histogram.forEach((key, value) -> System.out.println(key + " :: " + value));
result
3 :: 4
4 :: 3
5 :: 3
6 :: 2
当你想获取直方图的最大值3 :: 4
时
example
Map.Entry<Integer, Long> maxValue =
histogram.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElseThrow();
System.out.println("maxValue = " + maxValue);
jdk6新增record
,用record
来代替上面Map.Entry
更具有可读性
example
record NumberOfLength(int length, long number) {
static NumberOfLength fromEntry(Map.Entry<Integer, Long> entry) {
return new NumberOfLength(entry.getKey(), entry.getValue());
}
static Comparator<NumberOfLength> comparingByLength() {
return Comparator.comparing(NumberOfLength::number);
}
}
NumberOfLength maxNumberOfLength =
histogram.entrySet().stream()
.map(NumberOfLength::fromEntry)
.max(NumberOfLength.comparingByLength())
.orElseThrow();
System.out.println("maxNumberOfLength = " + maxNumberOfLength);
result
maxNumberOfLength = NumberOfLength[length=3, number=4]
提取不明确的最大值
example
Collection<String> strings =
List.of("two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
Map<Integer, Long> histogram =
strings.stream()
.collect(
Collectors.groupingBy(
String::length,
Collectors.counting()));
histogram.forEach((key, value) -> System.out.println(key + " :: " + value));
result
3 :: 3
4 :: 3
5 :: 3
6 :: 2
如果使用之前的方法,则会提取一个返回,隐藏其他2个。
使用中间Collectors
Collector API同样提供了一些中间操作:映射、过滤和flatmapping(平面映射)。
In fact, these special collectors cannot be created alone. The factory methods that you can use to create them all need a downstream collector as a second argument.
So, the overall collector you can create with these methods is a combination of an intermediate operation and a terminal operation.
通过Collector
映射
example
Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve");
List<String> result =
strings.stream()
.collect(
Collectors.mapping(String::toUpperCase, Collectors.toList()));
System.out.println("result = " + result);
可以使用中间操作来解决上面提取不明确最大值的问题。
通过Collector
过滤和flatmapping
Collectors.filtering()
Collectors.flatMapping()
使用终端Collectors
There Collector API also offers several terminal operations that correspond to terminal operations available on the Stream API.
-
maxBy()
,minBy
-
summingInt()
,summingLong()
,summingDouble()
-
averagingInt()
,averagingLong()
,averagingDouble()