Java 8收集器指南
1.概述
在本教程中,我們將介紹Java 8的收集器,這些收集器將在處理Stream
的最後一步中使用。
如果您想了解有關Stream
API本身的更多信息,請查看本文。
如果要查看如何利用收集器的功能進行並行處理,請檢查此項目。
2. Stream.collect()
方法
Stream.collect()
是Java 8的Stream API
的終端方法之一。它允許我們對Stream
實例中保存的數據元素執行可變的折疊操作(將元素重新打包到某些數據結構並應用一些其他邏輯,將它們串聯等)。
該操作的策略是通過Collector
接口實現提供的。
3. Collectors
所有預定義的實現都可以在Collectors
類中找到。通常的做法是將以下靜態導入與它們結合使用,以提高可讀性:
import static java.util.stream.Collectors.*;
或您選擇的單個導入收集器:
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
在以下示例中,我們將重用以下列表:
List<String> givenList = Arrays.asList("a", "bb", "ccc", "dd");
3.1。 Collectors.toList()
ToList
收集器可用於將所有Stream
元素收集到List
實例中。要記住的重要一點是,我們不能使用此方法來假設任何特定的List
實現。如果要對此進行更多控制,請改用toCollection
。
讓我們創建一個代表一系列元素的Stream
實例,並將它們收集到一個List
實例中:
List<String> result = givenList.stream()
.collect(toList());
3.1.1。 Collectors.toUnmodifiableList()
Java 10引入了一種方便的方法來將Stream
元素累積到不可修改的List
:
List<String> result = givenList.stream()
.collect(toUnmodifiableList());
如果現在嘗試修改result
List
,我們將得到UnsupportedOperationException
:
assertThatThrownBy(() -> result.add("foo"))
.isInstanceOf(UnsupportedOperationException.class);
3.2。 Collectors.toSet()
ToSet
收集器可用於將所有Stream
元素收集到Set
實例中。要記住的重要一點是,我們不能使用此方法假定任何特定的Set
實現。如果要對此進行更多控制,可以使用toCollection
。
讓我們創建一個代表一系列元素的Stream
實例,並將它們收集到Set
實例中:
Set<String> result = givenList.stream()
.collect(toSet());
Set
不包含重複的元素。如果我們的集合包含彼此相等的元素,則它們僅在結果Set
出現一次:
List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
Set<String> result = listWithDuplicates.stream().collect(toSet());
assertThat(result).hasSize(4);
3.2.1。 Collectors.toUnmodifiableSet()
從Java 10開始,我們可以使用toUnmodifiableSet()
收集器輕鬆創建一個不可修改的Set
:
Set<String> result = givenList.stream()
.collect(toUnmodifiableSet());
任何修改result Set
嘗試都將以UnsupportedOperationException
結尾:
assertThatThrownBy(() -> result.add("foo"))
.isInstanceOf(UnsupportedOperationException.class);
3.3。 Collectors.toCollection()
您可能已經註意到,在使用toSet and toList
收集器時,您無法對其實現進行任何假設。如果要使用自定義實現,則需要將toCollection
收集器與您選擇的提供的收集一起使用。
讓我們創建一個代表一系列元素的Stream
實例,並將它們收集到LinkedList
實例中:
List<String> result = givenList.stream()
.collect(toCollection(LinkedList::new))
請注意,這不適用於任何不可變的集合。在這種情況下,您將需要編寫自定義的Collector
實現或使用collectingAndThen
。
3.4。 Collectors
。 toMap()
ToMap
收集器可用於將Stream
元素收集到Map
實例中。為此,我們需要提供兩個功能:
- keyMapper
- valueMapper
keyMapper
將用於從Stream
元素中提取Map
鍵, valueMapper
將用於提取與給定鍵關聯的值。
讓我們將這些元素收集到一個Map
,該Map
將字符串存儲為鍵,並將其長度存儲為值:
Map<String, Integer> result = givenList.stream()
.collect(toMap(Function.identity(), String::length))
Function.identity()
只是用於定義接受和返回相同值的函數的快捷方式。
如果我們的集合包含重複元素,會發生什麼?與toSet
相反, toMap
不會靜默過濾重複項。這是可以理解的–應該如何確定該鍵選擇哪個值?
List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
assertThatThrownBy(() -> {
listWithDuplicates.stream().collect(toMap(Function.identity(), String::length));
}).isInstanceOf(IllegalStateException.class);
請注意, toMap
甚至不會評估值是否也相等。如果看到重複的鍵,則會立即引發IllegalStateException
。
在發生鍵衝突的情況下,我們應該將toMap
與另一個簽名一起使用:
Map<String, Integer> result = givenList.stream()
.collect(toMap(Function.identity(), String::length, (item, identicalItem) -> item));
這裡的第三個參數是BinaryOperator
,我們可以在其中指定希望如何處理衝突。在這種情況下,我們將只選擇這兩個衝突值中的任何一個,因為我們知道相同的字符串也將始終具有相同的長度。
3.4.1。 Collectors.toUnmodifiableMap()
與List
和Set
相似,Java 10引入了一種簡單的方法來將Stream
元素收集到不可修改的Map
:
Map<String, Integer> result = givenList.stream()
.collect(toMap(Function.identity(), String::length))
如我們所見,如果我們嘗試將新條目放入result Map
,則會得到UnsupportedOperationException
:
assertThatThrownBy(() -> result.put("foo", 3))
.isInstanceOf(UnsupportedOperationException.class);
3.5。 Collectors
.c ollectingAndThen()
CollectingAndThen
是一個特殊的收集器,允許在收集結束後立即對結果執行其他操作。
讓我們將Stream
元素收集到List
實例,然後將結果轉換為ImmutableList
實例:
List<String> result = givenList.stream()
.collect(collectingAndThen(toList(), ImmutableList::copyOf))
3.6。 Collectors
.joining()
Joining
收集器可用於聯接Stream<String>
元素。
我們可以通過以下方式將他們加入一起:
String result = givenList.stream()
.collect(joining());
這將導致:
"abbcccdd"
您還可以指定自定義分隔符,前綴,後綴:
String result = givenList.stream()
.collect(joining(" "));
這將導致:
"a bb ccc dd"
或者您可以寫:
String result = givenList.stream()
.collect(joining(" ", "PRE-", "-POST"));
這將導致:
"PRE-a bb ccc dd-POST"
3.7。 Collectors
.counting()
Counting
是一個簡單的收集器,可以簡單地計數所有Stream
元素。
現在我們可以寫:
Long result = givenList.stream()
.collect(counting());
3.8。 Collectors
.summaryizingDouble / Long / Int()
SummarizingDouble/Long/Int
是一個收集器,它返回一個特殊類,其中包含有關提取元素Stream
中數字數據的統計信息。
我們可以通過執行以下操作獲取有關字符串長度的信息:
DoubleSummaryStatistics result = givenList.stream()
.collect(summarizingDouble(String::length));
在這種情況下,將滿足以下條件:
assertThat(result.getAverage()).isEqualTo(2);
assertThat(result.getCount()).isEqualTo(4);
assertThat(result.getMax()).isEqualTo(3);
assertThat(result.getMin()).isEqualTo(1);
assertThat(result.getSum()).isEqualTo(8);
3.9。 Collectors.averagingDouble/Long/Int()
AveragingDouble/Long/Int
是一個收集器,僅返回提取元素的平均值。
我們可以通過執行以下操作來獲得平均字符串長度:
Double result = givenList.stream()
.collect(averagingDouble(String::length));
*3.10。 Collectors
.summingDouble / Long / Int()**
SummingDouble/Long/Int
是僅返回所提取元素之和的收集器。
通過執行以下操作,我們可以得出所有字符串長度的總和:
Double result = givenList.stream()
.collect(summingDouble(String::length));
3.11。 Collectors.maxBy()/minBy()
MaxBy
/ MinBy
收集器根據提供的Comparator
實例返回Stream
的最大/最小元素。
我們可以通過執行以下操作來選擇最大的元素:
Optional<String> result = givenList.stream()
.collect(maxBy(Comparator.naturalOrder()));
請注意,返回值包裝在Optional
實例中。這迫使用戶重新考慮空的收集箱。
3.12。 Collectors
. groupingBy()
GroupingBy
收集器用於按某些屬性對對象進行分組,並將結果存儲在Map
實例中。
我們可以按字符串長度對它們進行分組,並將分組結果存儲在Set
實例中:
Map<Integer, Set<String>> result = givenList.stream()
.collect(groupingBy(String::length, toSet()));
這將導致以下情況成立:
assertThat(result)
.containsEntry(1, newHashSet("a"))
.containsEntry(2, newHashSet("bb", "dd"))
.containsEntry(3, newHashSet("ccc"));
注意, groupingBy
方法的第二個參數是一個Collector
,您可以自由使用您選擇的任何Collector
。
3.13。 Collectors.partitioningBy()
PartitioningBy
是groupingBy
一種特殊情況,它接受一個Predicate
實例並將Stream
元素收集到一個Map
實例中,該實例將Boolean
值存儲為鍵,將集合存儲為值。在“ true”鍵下,您可以找到與給定Predicate
匹配的元素集合,在“ false”鍵下,您可以找到與給定Predicate
不匹配的元素集合。
你可以寫:
Map<Boolean, List<String>> result = givenList.stream()
.collect(partitioningBy(s -> s.length() > 2))
結果導致包含以下內容的地圖:
{false=["a", "bb", "dd"], true=["ccc"]}
3.14。 Collectors.teeing()
讓我們使用到目前為止學習到的收集器從給定Stream
找到最大和最小數目:
List<Integer> numbers = Arrays.asList(42, 4, 2, 24);
Optional<Integer> min = numbers.stream().collect(minBy(Integer::compareTo));
Optional<Integer> max = numbers.stream().collect(maxBy(Integer::compareTo));
// do something useful with min and max
在這裡,我們使用了兩個不同的收集器,然後將這兩個收集器的結果相結合以創建有意義的東西。在Java 12之前,為了涵蓋此類用例,我們必須對給定的Stream
兩次操作,將中間結果存儲到臨時變量中,然後再組合這些結果。
幸運的是,Java 12提供了一個內置的收集器,可以代表我們執行這些步驟:我們要做的就是提供兩個收集器和合併器功能。
由於這種新的收藏家開杆朝兩個不同的方向給定的流,這就是所謂的teeing:
numbers.stream().collect(teeing(
minBy(Integer::compareTo), // The first collector
maxBy(Integer::compareTo), // The second collector
(min, max) -> // Receives the result from those collectors and combines them
));
該示例可在GitHub的core-java-12項目中找到。
4.定制Collector
如果要編寫Collector實現,則需要實現Collector接口並指定其三個通用參數:
public interface Collector<T, A, R> {...}
- T –可用於收集的對像類型,
- A –可變累加器對象的類型,
- R –最終結果的類型。
讓我們寫一個示例收集器,將元素收集到ImmutableSet
實例中。我們首先指定正確的類型:
private class ImmutableSetCollector<T>
implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {...}
由於我們需要一個可變的集合來進行內部集合操作處理,因此我們不能為此使用ImmutableSet
。我們需要使用其他可變的集合或可以為我們臨時累積對象的任何其他類。
在這種情況下,我們將繼續使用ImmutableSet.Builder
,現在我們需要實現5種方法:
-
Supplier<ImmutableSet.Builder<T>> **supplier** ()
-
BiConsumer<ImmutableSet.Builder<T>, T> **accumulator** ()
-
BinaryOperator<ImmutableSet.Builder<T>> **combiner** ()
-
Function<ImmutableSet.Builder<T>, ImmutableSet<T>> **finisher** ()
-
Set<Characteristics> **characteristics** ()
**The supplier()
**方法返回一個Supplier
實例,該實例生成一個空的累加器實例,因此,在這種情況下,我們可以簡單地編寫:
@Override
public Supplier<ImmutableSet.Builder<T>> supplier() {
return ImmutableSet::builder;
}
**The accumulator()
**方法返回一個函數,該函數用於將新元素添加到現有的accumulator
像中,因此我們僅使用Builder
的add
方法。
@Override
public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {
return ImmutableSet.Builder::add;
}
**The combiner()**
方法返回一個函數,該函數用於將兩個累加器合併在一起:
@Override
public BinaryOperator<ImmutableSet.Builder<T>> combiner() {
return (left, right) -> left.addAll(right.build());
}
**The finisher()**
方法返回一個函數,該函數用於將累加器轉換為最終結果類型,因此在這種情況下,我們將僅使用Builder
的build
方法:
@Override
public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {
return ImmutableSet.Builder::build;
}
**The characteristics()**
方法用於為Stream提供一些其他信息,這些信息將用於內部優化。在這種情況下,我們不注意Set
的元素順序,因此我們將使用Characteristics.UNORDERED
。要獲取有關此主題的更多信息,請選中“ Characteristics
”'JavaDoc。
@Override public Set<Characteristics> characteristics() {
return Sets.immutableEnumSet(Characteristics.UNORDERED);
}
這是完整的實現及其用法:
public class ImmutableSetCollector<T>
implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {
@Override
public Supplier<ImmutableSet.Builder<T>> supplier() {
return ImmutableSet::builder;
}
@Override
public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {
return ImmutableSet.Builder::add;
}
@Override
public BinaryOperator<ImmutableSet.Builder<T>> combiner() {
return (left, right) -> left.addAll(right.build());
}
@Override
public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {
return ImmutableSet.Builder::build;
}
@Override
public Set<Characteristics> characteristics() {
return Sets.immutableEnumSet(Characteristics.UNORDERED);
}
public static <T> ImmutableSetCollector<T> toImmutableSet() {
return new ImmutableSetCollector<>();
}
在這裡:
List<String> givenList = Arrays.asList("a", "bb", "ccc", "dddd");
ImmutableSet<String> result = givenList.stream()
.collect(toImmutableSet());
5.結論
在本文中,我們深入探討了Java 8的收集器,並展示瞭如何實現它。確保檢查我的一個項目,該項目可以增強Java並行處理的功能。