collect
應該說是Stream
中最強大的終端操作了,使用其幾乎能得到你想要的任意資料的聚合,下面好好分析該工具的用法.
在Stream介面中有如下兩個方法
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);複製程式碼
很明顯第一種相當於簡易實現版本,第二種為高階用法.更多更復雜的操作都封裝到Collector介面中,並提供一些靜態方法供使用者呼叫.下面逐一分析.
簡易呼叫形式
簡易呼叫形式就是第一種介面,介面如下
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);複製程式碼
呼叫方式如下,很明顯第一個引數supplier
為結果存放容器,第二個引數accumulator
為結果如何新增到容器的操作,第三個引數combiner
則為多個容器的聚合策略.
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,StringBuilder::append).toString();
//等價於上面,這樣看起來應該更加清晰
String concat = stringStream.collect(() -> new StringBuilder(),(l, x) -> l.append(x), (r1, r2) -> r1.append(r2)).toString();複製程式碼
那麼換一種,我想對一個List收集結果總和,按照Collect的要求,首先需要容器sum,然後新增操作 sum+x,聚合操作,sum1+sum2,那麼就很容易寫出來了,看完下面程式碼後好好體會下,然後再看高階用法.當然用sum方法收集是最佳解決方案,這裡只是提供一種示例應用.
// 由於基本型別都是不可變型別,所以這裡用陣列當做容器
final Integer[] integers = Lists.newArrayList(1, 2, 3, 4, 5)
.stream()
.collect(() -> new Integer[]{0}, (a, x) -> a[0] += x, (a1, a2) -> a1[0] += a2[0]);複製程式碼
那麼再換一種,有一個Person
類,其擁有type與name兩個屬性,那麼使用collect
把他收集到Map集合中,其中鍵為type,值為person的集合.如下程式碼所示,看明白了相信就掌握了該方法.
Lists.<Person>newArrayList().stream()
.collect(() -> new HashMap<Integer,List<Person>>(),
(h, x) -> {
List<Person> value = h.getOrDefault(x.getType(), Lists.newArrayList());
value.add(x);
h.put(x.getType(), value);
},
HashMap::putAll
);複製程式碼
Collector高階呼叫
Collector
介面是使得collect
操作強大的終極武器,對於絕大部分操作可以分解為旗下主要步驟,提供初始容器->加入元素到容器->併發下多容器聚合->對聚合後結果進行操作,同時Collector
介面又提供了of
靜態方法幫助你最大化的定製自己的操作,官方也提供了Collectors
這個類封裝了大部分的常用收集操作.
另外CollectorImpl
為Collector
的實現類,因為介面不可例項化,這裡主要完成例項化操作.
//初始容器
Supplier<A> supplier();
//加入到容器操作
BiConsumer<A, T> accumulator();
//多容器聚合操作
BinaryOperator<A> combiner();
//聚合後的結果操作
Function<A, R> finisher();
//操作中便於優化的狀態欄位
Set<Characteristics> characteristics();複製程式碼
Collectors的方法封裝
Collectors
作為官方提供的收集工具類,那麼其很多操作都具有參考性質,能幫助我們更加理解Collector
介面,萬變不離其宗,最終只是上面五個函式介面的混合操作,下面來分析下官方是如何使用這幾個介面的.
toList()
容器: ArrayList::new
加入容器操作: List::add
多容器合併: left.addAll(right); return left;
聚合後的結果操作: 這裡直接返回,因此無該操作,預設為castingIdentity()
優化操作狀態欄位: CH_ID
這樣看起來很簡單,那麼對於Map,Set等操作都是類似的實現.
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}複製程式碼
joining()
容器: StringBuilder::new
加入容器操作: StringBuilder::append
多容器合併: r1.append(r2); return r1;
聚合後的結果操作: StringBuilder::toString
優化操作狀態欄位: CH_NOID
public static Collector<CharSequence, ?, String> joining() {
return new CollectorImpl<CharSequence, StringBuilder, String>(
StringBuilder::new, StringBuilder::append,
(r1, r2) -> { r1.append(r2); return r1; },
StringBuilder::toString, CH_NOID);
}複製程式碼
下面來個複雜的
groupingBy()
groupingBy
是toMap
的一種高階方式,彌補了toMap
對值無法提供多元化的收集操作,比如對於返回Map<T,List<E>>
這樣的形式toMap
就不是那麼順手,那麼groupingBy
的重點就是對Key和Value值的處理封裝.分析如下程式碼,其中classifier
是對key值的處理,mapFactory
則是指定Map的容器具體型別,downstream
為對Value的收集操作,具體程式碼這裡不做分析,無非是把值一個一個的put進指定容器.
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
.......
}複製程式碼
對於之前用原生collect
方法做的收集操作那麼就可以很容易改寫為groupBy形式
//原生形式
Lists.<Person>newArrayList().stream()
.collect(() -> new HashMap<Integer,List<Person>>(),
(h, x) -> {
List<Person> value = h.getOrDefault(x.getType(), Lists.newArrayList());
value.add(x);
h.put(x.getType(), value);
},
HashMap::putAll
);
//groupBy形式
Lists.<Person>newArrayList().stream()
.collect(Collectors.groupingBy(Person::getType, HashMap::new, Collectors.toList()));
//因為對值有了操作,因此我可以更加靈活的對值進行轉換
Lists.<Person>newArrayList().stream()
.collect(Collectors.groupingBy(Person::getType, HashMap::new, Collectors.mapping(Person::getName,Collectors.toSet())));複製程式碼
reducing()
reducing
是針對單個值的收集,其返回結果不是集合家族的型別,而是單一的實體類T
容器: boxSupplier(identity)
,這裡包裹用的是一個長度為1的Object[]陣列,至於原因自然是不可變型別的鍋
加入容器操作: a[0] = op.apply(a[0], t)
多容器合併: a[0] = op.apply(a[0], b[0]); return a;
聚合後的結果操作: 結果自然是Object[0]所包裹的資料a -> a[0]
優化操作狀態欄位: CH_NOID
那麼看到這裡困惑是不是有一種恍然大悟的感覺,反正我是有的.
public static <T> Collector<T, ?, T>
reducing(T identity, BinaryOperator<T> op) {
return new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], t); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0],
CH_NOID);
}複製程式碼
那麼接下來就是對之前Collect的一些操作的改造
//原生操作
final Integer[] integers = Lists.newArrayList(1, 2, 3, 4, 5)
.stream()
.collect(() -> new Integer[]{0}, (a, x) -> a[0] += x, (a1, a2) -> a1[0] += a2[0]);
//reducing操作
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
.stream()
.collect(Collectors.reducing(0, Integer::sum));
//當然Stream也提供了reduce操作
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
.stream().reduce(0, Integer::sum)複製程式碼
可能遇到的問題
記錄下生產中使用該工具遇到的一些小錯誤
toMap所產生的異常
toMap的操作主要如下程式碼,異常來自兩個方面
- 操作呼叫的是
map.merge
方法,該方法遇到value為null的情況會報npe,即使你使用的是hashMap可以接受null值,也照樣報.搞不懂這裡為什麼這樣設計. - 未指定衝突合併策略,也就是第三個引數
BinaryOperator<U> mergeFunction
時遇到重複的key會直接拋IllegalStateException
,因此需要注意.
總結
到此對於collect
的操作應該就很清晰了,希望通過這些例子能掌握核心,也就是Collector
介面中那幾個函式的作用,希望對你有幫助.
個人部落格 mrdear.cn ,歡迎交流