1. 程式人生 > >Java8 函數語言程式設計 筆記-收集器基本用法

Java8 函數語言程式設計 筆記-收集器基本用法

       參考並建議閱讀《Java8函數語言程式設計》 /《java 8 實戰》-------------------------------------------------------------------------------------------------------------------------------------------------------------     收集器,一種通用的,從流生成的複雜的值的結構。只要將它傳給collect方法,所有的流都可以使用它了。標準類庫已經提供了一些有用的收集器,java.util.stream.Collectors類中提供了相關的方法。

使用收集器

   轉換成其他集合

      有一些收集器可以生成其他集合,比如,toList, toSet, toCollection. 總有一些時候,需要生成一個集合:      1)已有程式碼是為集合編寫的,因此需要將流轉換成集合傳入      2)在集合上進行一系列鏈式操作後,最終希望生成一個值      3)寫單元測試時,需要對某個具體的集合做斷言      呼叫toList或者toSet方法時,不需要指定具體的型別,Stream類庫在背後已經自動挑選出了合適的型別。      如果希望使用一個特定的集合收集值,比如希望使用TreeSet,而不是框架指定的Set, 可以使用toCollection接受一個函式作為引數,來建立集合。
IntStream.range(0,5).boxed().collect(Collectors.toCollection(TreeSet::new));

   轉換成值

       可以利用收集器讓流生成一個值。如maxBy,minBy和averagingInt.       
IntStream.range(0,5).boxed().collect(Collectors.maxBy(Comparator.comparing(x->x)))

   資料分塊

      另外一個常用的流操作是將其分解成兩個集合。partitioningBy,接受一個流,並將其分成兩部分,使用Predicate物件判斷一個元素應該屬於哪個部分,並根據布林值返回一個Map到列表。因此,對於true List中的元素,Predicate返回true,對於其他List中的元素,Predicate返回false。如下圖所示。       

   資料分組

   資料分組是一種更自然的分割資料操作,與將資料分成true和false兩部分不同,可以使用任意值對資料分組。groupingBy收集器接受一個分類函式,用來對資料分組,就像partitionBy一樣,接受一個Predicate物件將資料分成true和false兩部分。用的分類器時一個Function物件,和map操作用到的一樣。                                                                       表 1 Collectors類的靜態工廠方法

工廠方法

返回型別

用於

 toList

List<T>

把流中所有專案收集到一個List

使用示例: List<Dish> dishes = menuStream.collect(toList());

toSet

Set<T>

把流中所有專案收集到一個 Set,刪除重複項

使用示例: Set<Dish> dishes = menuStream.collect(toSet());

toCollection

Collection<T>

把流中所有專案收集到給定的供應源建立的集合

使用示例: Collection<Dish> dishes = menuStream.collect(toCollection(),ArrayList::new);

counting

Long

計算流中元素的個數

使用示例: long howManyDishes = menuStream.collect(counting());

summingInt

Integer

對流中專案的一個整數屬性求和

使用示例: int totalCalories = menuStream.collect(summingInt(Dish::getCalories))

averagingInt

Double

計算流中專案 Integer 屬性的平均值

使用示例: double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));

summarizingInt

IntSummaryStatistics

收集關於流中專案 Integer 屬性的統計值,例如最大、最小、總和與平均值

使用示例 :               

IntSummaryStatistics menuStatistics=menuStream.collect(summarizingInt(Dish::getCalories));

joining

String

連線對流中每個專案呼叫 toString 方法所生成的字串

使用示例: String shortMenu =menuStream.map(Dish::getName).collect(joining(", "));

maxBy

Optional<T>

一個包裹了流中按照給定比較器選出的最大元素的 Optional,或如果流為空則為 Optional.empty()

使用示例:

 Optional<Dish> fattest =menuStream.collect(maxBy(comparingInt(Dish::getCalories)));

minBy

Optional<T>

一個包裹了流中按照給定比較器選出的最小元素的 Optional,或如果流為空則為 Optional.empty()

使用示例:

Optional<Dish> lightest =menuStream.collect(minBy(comparingInt(Dish::getCalories)));

reducing

歸約操作產生的型別

從一個作為累加器的初始值開始,利用 BinaryOperator 與流中的元素逐個結合,從而將流歸約為單個值

使用示例:

int totalCalories =menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));

collectingAndThen

轉換函式返回的型別

包裹另一個收集器,對其結果應用轉換函式

使用示例:

int howManyDishes =menuStream.collect(collectingAndThen(toList(), List::size)

groupingBy

Map<K, List<T>>

根據專案的一個屬性的值對流中的專案作問組,並將屬性值作為結果 Map 的鍵

使用示例:

Map<Dish.Type,List<Dish>>dishesByType =menuStream.collect(groupingBy(Dish::getType));

partitioningBy

Map<Boolean,List<T>>

根據對流中每個專案應用謂詞的結果來對專案進行分割槽

使用示例:

Map<Boolean,List<Dish>> vegetarianDishes

=menuStream.collect(partitioningBy(Dish::isVegetarian));

組合收集器

各種收集器已經很強大,但是仍然可以將它們組合起來使用,如下面這個例子:
    public Map<Artist,Long> numberOfAlbums(Stream<Album> albums){
        return albums.collect(groupingBy(album->album.getMainMusician(),counting()));
    }
 groupingBy先將元素分成塊,每塊都與分類函式getMainMusician提供的鍵值相關聯,然後使用下游的另一個收集器收集每塊中的元素,最後將結果對映為一個Map.還可以看下面這個例子mapping收集器和map方法一樣,接受一個Function物件作為引數,如:
    public Map<Artist,List<String>> numberOfAlbums(Stream<Album> albums){
        return albums.collect(groupingBy(album->album.getMainMusician(),mapping(Album::getName,toList())));
    }
這兩個例子都使用了第二個收集器,用以收集最終結果額一個子集,這樣的收集器叫下游收集器。主收集器中會用到下游收集器,這種方式叫做組合使用收集器。

收集器介面

Collector介面包含了一系列方法,為實現具體的歸約操作範本,通過實踐Collector介面我們可以定製自己的收集器。 Collector介面:
public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
    Set<java.util.stream.Collector.Characteristics> characteristics();
}
T 是流中要收集的專案泛型A 是累加器的型別,累加器是在收集過程中用於累積部分結果的物件。R 是收集操作得到的物件的型別例如,實現一個ToListCollector<T>類,將Stream<T>中所有元素收集到一個List<T>裡,簽名:
public class ToListCollector<T> implements Collector<T, List<T>, List<T>>
下面說明一下Collector的各個方法:1. 建立新的結果容器: supplier方法
  supplier方法必須返回一個結果為空的Supplier,也就是一個無引數函式,在呼叫時它會建立一個空的累加器例項,供資料收集過程使用。很明顯,對於將累加器本身作為結果返回的收集器,比如我們的ToListCollector,在對空流執行操作的時候,這個空的累加器也代表了收集過程的結果。在我們的ToListCollector中,supplier返回一個空的List,如下所示:
public Supplier<List<T>> supplier() {
     return () -> new ArrayList<T>();
}
2. 將元素新增到結果容器: accumulator方法
   accumulator方法會返回執行歸約操作的函式。當遍歷到流中第n個元素時,這個函式執行時會有兩個引數:儲存歸約結果的累加器(已收集了流中的前 n-1 個專案), 還有第n個元素本身。該函式將返回void,因為累加器是原位更新,即函式的執行改變了它的內部狀態以體現遍歷的元素的效果。對於ToListCollector,這個函式僅僅會把當前專案新增至已經遍歷過的專案的列表:

public BiConsumer<List<T>, T> accumulator() {
     return (list, item) -> list.add(item);
}
3. 對結果容器應用最終轉換: finisher方法
在遍歷完流後, finisher方法必須返回在累積過程的最後要呼叫的一個函式,以便將累加器物件轉換為整個集合操作的最終結果。通常,就像ToListCollector的情況一樣,累加器物件恰好符合預期的最終結果,因此無需進行轉換。所以finisher方法只需返回identity函式: 

public Function<List<T>, List<T>> finisher() {
     return Function.identity();
}
4. 合併兩個結果容器: combiner方法
四個方法中的最後一個——combiner方法會返回一個供歸約操作使用的函式,它定義了對流的各個子部分進行並行處理時,各個子部分歸約所得的累加器要如何合併。對於toList而言,這個方法的實現非常簡單,只要把從流的第二個部分收集到的專案列表加到遍歷第一部分時得到的列表後面就行了: 

public BinaryOperator<List<T>> combiner() {
     return (list1, list2) -> {
              list1.addAll(list2);
              return list1; }
}

                                                                                                             順序歸約
                                                                                 combiner並行化歸約