Java?8中的Collectors?API介紹
Stream.Collect() 方法
Stream.collect()
是Java 8的流API的終端方法之一。它允許我們對流實例中保存的數(shù)據(jù)元素執(zhí)行可變折疊操作(將元素重新打包到某些數(shù)據(jù)結(jié)構(gòu),并應用一些附加邏輯,將它們連接起來,等等)。
此操作的策略通過收集器接口實現(xiàn)提供。
Collectors
所有預定義的實現(xiàn)都可以在Collectors
類中找到。通常使用以下靜態(tài)導入來提高可讀性:
import static java.util.stream.Collectors.*;
我們也可以使用我們選擇的單一導入收集器collectors:
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet;
在以下示例中,我們將重用以下list:
List<String> givenList = Arrays.asList("a", "bb", "ccc", "dd");
Collectors.ToList()
toList
收集器可用于將所有流元素收集到列表實例中。需要記住的重要一點是,我們不能用這種方法假設(shè)任何特定的列表實現(xiàn)。如果我們想對此有更多的控制,我們可以使用toCollection
。
讓我們創(chuàng)建一個表示元素序列的流實例,然后將它們收集到一個列表實例中:
List<String> result = givenList.stream() .collect(toList());
Collectors.ToUnmodifiableList()
Java 10引入了一種方便的方法,將流元素累積到一個不可修改的列表中:
List<String> result = givenList.stream() .collect(toUnmodifiableList());
現(xiàn)在,如果我們試圖修改結(jié)果列表,我們將得到一個UnsupportedOperationException:
assertThatThrownBy(() -> result.add("foo")) .isInstanceOf(UnsupportedOperationException.class);
Collectors.ToSet()
toSet
收集器可用于將所有流元素收集到集合實例中。需要記住的重要一點是,我們不能用這種方法假設(shè)任何特定的集合實現(xiàn)。如果我們想對此有更多的控制,我們可以使用toCollection
。
讓我們創(chuàng)建一個表示元素序列的流實例,然后將它們收集到一個集合實例中:
Set<String> result = givenList.stream() .collect(toSet());
集合不包含重復的元素。如果我們的集合包含彼此相等的元素,則它們只在結(jié)果集中出現(xiàn)一次:
List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb"); Set<String> result = listWithDuplicates.stream().collect(toSet()); assertThat(result).hasSize(4);
Collectors.ToUnmodifiableSet()
自Java 10以來,我們可以使用toUnmodifiableSet()
收集器輕松創(chuàng)建一個不可修改的集:
Set<String> result = givenList.stream() .collect(toUnmodifiableSet());
任何修改結(jié)果集的嘗試都將以不支持操作異常告終:
assertThatThrownBy(() -> result.add("foo")) .isInstanceOf(UnsupportedOperationException.class);
Collectors.ToCollection()
正如我們已經(jīng)指出的,在使用toSet
和toList
收集器時,我們不能對它們的實現(xiàn)進行任何假設(shè)。如果我們想使用自定義實現(xiàn),我們需要將toCollection
收集器與我們選擇的提供的集合一起使用。
讓我們創(chuàng)建一個表示元素序列的流實例,然后將它們收集到LinkedList
實例中:
List<String> result = givenList.stream() .collect(toCollection(LinkedList::new))
請注意,這不適用于任何不可變的集合。在這種情況下,我們需要編寫自定義收集器實現(xiàn)或使用CollectionAndThen
。
Collectors.ToMap()
toMap
收集器可用于將流元素收集到映射實例中。為此,我們需要提供兩個功能:
keyMapper
valueMapper
我們將使用keyMapper從流元素中提取映射鍵,使用valueMapper提取與給定鍵關(guān)聯(lián)的值。
讓我們將這些元素收集到一個映射中,該映射將字符串存儲為鍵,長度存儲為值:
Map<String, Integer> result = givenList.stream() .collect(toMap(Function.identity(), String::length))
Function.identity()
只是定義接受并返回相同值的函數(shù)的快捷方式。
那么,如果我們的集合包含重復的元素,會發(fā)生什么呢?與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
甚至不計算這些值是否相等。如果它看到重復的key,它會立即拋出一個非法狀態(tài)異常。
在key沖突的情況下,我們應該使用另一個簽名的toMap
:
Map<String, Integer> result = givenList.stream() .collect(toMap(Function.identity(), String::length, (item, identicalItem) -> item));
這里的第三個參數(shù)是BinaryOperator
,我們可以在其中指定希望如何處理碰撞。在本例中,我們只選擇這兩個沖突的值中的任何一個,因為我們知道相同的字符串也總是具有相同的長度。
Collectors.ToUnmodifiableMap()
與列表和集合類似,Java 10引入了一種將流元素收集到不可修改映射中的簡單方法:
Map<String, Integer> result = givenList.stream() .collect(toMap(Function.identity(), String::length))
正如我們所見,如果我們試圖在結(jié)果映射中添加一個新條目,我們將得到一個不支持的操作異常:
assertThatThrownBy(() -> result.put("foo", 3)) .isInstanceOf(UnsupportedOperationException.class);
Collectors.CollectingAndThen()
CollectionAndThen
是一個特殊的收集器,允許我們在收集結(jié)束后立即對結(jié)果執(zhí)行另一個操作。
讓我們將流元素收集到列表實例,然后將結(jié)果轉(zhuǎn)換為ImmutableList
實例:
List<String> result = givenList.stream() .collect(collectingAndThen(toList(), ImmutableList::copyOf))
Collectors.Joining()
Joining
收集器可用于joining Stream<String>
元素。
我們可以通過以下方式將它們結(jié)合在一起:
String result = givenList.stream() .collect(joining());
結(jié)果:
"abbcccdd"
我們還可以指定自定義分隔符、前綴和后綴:
String result = givenList.stream() .collect(joining(" "));
結(jié)果:
"a bb ccc dd"
也可這樣寫:
String result = givenList.stream() .collect(joining(" ", "PRE-", "-POST"));
結(jié)果:
"PRE-a bb ccc dd-POST"
Collectors.Counting()
Counting
是一個簡單的收集器,允許對所有流元素進行計數(shù)。
現(xiàn)在我們可以寫:
Long result = givenList.stream() .collect(counting());
Collectors.SummarizingDouble/Long/Int()
SummaringDouble/Long/Int
是一個收集器,它返回一個特殊的類,該類包含有關(guān)提取元素流中數(shù)字數(shù)據(jù)的統(tǒng)計信息。
我們可以通過以下操作獲得有關(guān)字符串長度的信息:
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);
Collectors.AveragingDouble/Long/Int()
AveragingDouble/Long/Int
是一個收集器,它只返回提取元素的平均值。
我們可以通過以下操作獲得平均字符串長度:
Double result = givenList.stream() .collect(averagingDouble(String::length));
Collectors.SummingDouble/Long/Int()
SummingDouble/Long/Int是一個收集器,它只返回提取元素的總和。
我們可以通過以下操作得到所有字符串長度的總和:
Double result = givenList.stream() .collect(summingDouble(String::length));
Collectors.MaxBy()/MinBy()
MaxBy/MinBy
收集器根據(jù)提供的比較器實例返回流的最大/最小元素。
我們可以通過以下方式選擇最大的元素:
Optional<String> result = givenList.stream() .collect(maxBy(Comparator.naturalOrder()));
我們可以看到返回的值被包裝在一個可選的實例中。這迫使用戶重新考慮空的收集角落案例。
Collectors.GroupingBy()
GroupingBy collector用于按某些屬性對對象進行分組,然后將結(jié)果存儲在Map實例中。
我們可以按字符串長度對它們進行分組,并將分組結(jié)果存儲在集合實例中:
Map<Integer, Set<String>> result = givenList.stream() .collect(groupingBy(String::length, toSet()));
結(jié)果是true:
assertThat(result) .containsEntry(1, newHashSet("a")) .containsEntry(2, newHashSet("bb", "dd")) .containsEntry(3, newHashSet("ccc"));
我們可以看到groupingBy
方法的第二個參數(shù)是收集器。此外,我們可以自由使用我們選擇的任何收集器。
Collectors.PartitioningBy()
PartitioningBy是groupingBy的一種特殊情況,它接受謂詞實例,然后將流元素收集到Map實例中,Map實例將布爾值存儲為鍵,將集合存儲為值。在“true”鍵下,我們可以找到與給定謂詞匹配的元素集合,在“false”鍵下,我們可以找到與給定謂詞不匹配的元素集合。
我們可以寫:
Map<Boolean, List<String>> result = givenList.stream() .collect(partitioningBy(s -> s.length() > 2))
在Map中的結(jié)果:
{false=["a", "bb", "dd"], true=["ccc"]}
Collectors.Teeing()
讓我們使用到目前為止所學的收集器,從給定流中找出最大和最小數(shù):
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
在這里,我們使用兩個不同的收集器,然后將這兩個收集器的結(jié)果結(jié)合起來,創(chuàng)造出一些有意義的東西。在Java12之前,為了涵蓋此類用例,我們必須對給定流進行兩次操作,將中間結(jié)果存儲到臨時變量中,然后將這些結(jié)果合并。
幸運的是,Java12提供了一個內(nèi)置收集器,代表我們處理這些步驟;我們所要做的就是提供兩個采集器和組合器功能。
由于這種新的收集器將給定的流轉(zhuǎn)向兩個不同的方向,因此稱為T形:
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 ));
Custom Collectors
如果我們想編寫自己的收集器實現(xiàn),我們需要實現(xiàn)收集器接口,并指定其三個通用參數(shù):
public interface Collector<T, A, R> {...}
- T–可供收集的對象類型
- A–可變累加器對象的類型
- R–最終結(jié)果的類型
讓我們編寫一個示例收集器,用于將元素收集到ImmutableSet
實例中。我們首先指定正確的類型:
private class ImmutableSetCollector<T> implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {...}
因為我們需要一個可變集合來處理內(nèi)部集合操作,所以不能使用ImmutableSet
。相反,我們需要使用一些其他可變集合,或任何其他可以臨時為我們積累對象的類。在這種情況下,我們將使用ImmutableSet
。現(xiàn)在我們需要實現(xiàn)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()
supplier()方法返回一個生成空累加器實例的Supplier實例。所以在這種情況下,我們可以簡單地寫:
@Override public Supplier<ImmutableSet.Builder<T>> supplier() { return ImmutableSet::builder; }
acculator()
方法返回一個函數(shù),該函數(shù)用于向現(xiàn)有acculator
對象添加新元素。讓我們使用生成器的add
方法:
@Override public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() { return ImmutableSet.Builder::add; }
combiner()
方法返回一個用于將兩個累加器合并在一起的函數(shù):
@Override public BinaryOperator<ImmutableSet.Builder<T>> combiner() { return (left, right) -> left.addAll(right.build()); }
finisher()
方法返回一個函數(shù),用于將累加器轉(zhuǎn)換為最終結(jié)果類型。所以在這種情況下,我們只使用Builder
的構(gòu)建方法:
@Override public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() { return ImmutableSet.Builder::build; }
characteristics()
方法用于為Stream提供一些用于內(nèi)部優(yōu)化的附加信息。在這種情況下,我們不會注意元素在集合中的順序。
@Override public Set<Characteristics> characteristics() { return Sets.immutableEnumSet(Characteristics.UNORDERED); }
以下是完整的實現(xiàn)和用法:
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<>(); }
最后在action中:
List<String> givenList = Arrays.asList("a", "bb", "ccc", "dddd"); ImmutableSet<String> result = givenList.stream() .collect(toImmutableSet());
到此這篇關(guān)于Java 8中的Collectors API介紹的文章就介紹到這了,更多相關(guān) Collectors API 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java如何實現(xiàn)項目啟動時執(zhí)行指定方法
這篇文章主要為大家詳細介紹了java項目如何啟動時執(zhí)行指定方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Java OpenCV利用KNN算法實現(xiàn)圖像背景移除
這篇文章主要為大家介紹了Java OpenCV利用K最鄰近(KNN,K-NearestNeighbor)分類算法實現(xiàn)圖像背景移除的示例代碼,需要的可以參考一下2022-01-01簡析Java中的util.concurrent.Future接口
這篇文章主要介紹了簡析Java中的util.concurrent.Future接口,作者把future歸結(jié)為在未來得到目標對象的占位符,需要的朋友可以參考下2015-07-07Spring @Value 設(shè)置默認值的實現(xiàn)
這篇文章主要介紹了Spring @Value 設(shè)置默認值的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09